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.
This commit is contained in:
parent
ad921a0ee8
commit
454f58a7b1
@ -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> NULL_CONSTRUCTOR = createNullConstructor();
|
||||
|
||||
// Package private for testing purposes.
|
||||
final Cache<Class<?>, Constructor<?>> constructorCache;
|
||||
|
||||
public DefaultConstructorAllocator() {
|
||||
this(200);
|
||||
}
|
||||
|
||||
public DefaultConstructorAllocator(int cacheSize) {
|
||||
constructorCache = new LruCache<Class<?>, Constructor<?>>(cacheSize);
|
||||
}
|
||||
|
||||
private static final Constructor<Null> createNullConstructor() {
|
||||
try {
|
||||
return getNoArgsConstructor(Null.class);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T newInstance(Class<T> c) throws Exception {
|
||||
Constructor<T> constructor = findConstructor(c);
|
||||
return (constructor != null) ? constructor.newInstance() : null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> Constructor<T> findConstructor(Class<T> c) {
|
||||
Constructor<T> cachedElement = (Constructor<T>) constructorCache.getElement(c);
|
||||
if (cachedElement != null) {
|
||||
if (cachedElement == NULL_CONSTRUCTOR) {
|
||||
return null;
|
||||
} else {
|
||||
return cachedElement;
|
||||
}
|
||||
}
|
||||
|
||||
Constructor<T> noArgsConstructor = getNoArgsConstructor(c);
|
||||
if (noArgsConstructor != null) {
|
||||
constructorCache.addElement(c, noArgsConstructor);
|
||||
} else {
|
||||
constructorCache.addElement(c, NULL_CONSTRUCTOR);
|
||||
}
|
||||
return noArgsConstructor;
|
||||
}
|
||||
|
||||
private static <T> Constructor<T> getNoArgsConstructor(Class<T> c) {
|
||||
try {
|
||||
Constructor<T> declaredConstructor = c.getDeclaredConstructor();
|
||||
declaredConstructor.setAccessible(true);
|
||||
return declaredConstructor;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// placeholder class for Null constructor
|
||||
private static final class Null {
|
||||
}
|
||||
}
|
@ -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<InstanceCreator<?>> createDefaultInstanceCreators() {
|
||||
ParameterizedTypeHandlerMap<InstanceCreator<?>> map =
|
||||
new ParameterizedTypeHandlerMap<InstanceCreator<?>>();
|
||||
map.registerForTypeHierarchy(Map.class, MAP_TYPE_ADAPTER);
|
||||
DefaultConstructorAllocator allocator = new DefaultConstructorAllocator(50);
|
||||
|
||||
// Map Instance Creators
|
||||
map.registerForTypeHierarchy(Map.class,
|
||||
new DefaultConstructorCreator<Map>(LinkedHashMap.class, allocator));
|
||||
|
||||
// Add Collection type instance creators
|
||||
map.registerForTypeHierarchy(Collection.class, COLLECTION_TYPE_ADAPTER);
|
||||
DefaultConstructorCreator<List> listCreator =
|
||||
new DefaultConstructorCreator<List>(ArrayList.class, allocator);
|
||||
DefaultConstructorCreator<Queue> queueCreator =
|
||||
new DefaultConstructorCreator<Queue>(LinkedList.class, allocator);
|
||||
DefaultConstructorCreator<Set> setCreator =
|
||||
new DefaultConstructorCreator<Set>(HashSet.class, allocator);
|
||||
DefaultConstructorCreator<SortedSet> sortedSetCreator =
|
||||
new DefaultConstructorCreator<SortedSet>(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<Collection>,
|
||||
JsonDeserializer<Collection>, InstanceCreator<Collection> {
|
||||
JsonDeserializer<Collection> {
|
||||
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<Properties> {
|
||||
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<TreeSet<?>> {
|
||||
public TreeSet<?> createInstance(Type type) {
|
||||
return new TreeSet<Object>();
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return TreeSetCreator.class.getSimpleName();
|
||||
}
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
private static final class DefaultConstructorCreator<T> implements InstanceCreator<T> {
|
||||
private final Class<? extends T> defaultInstance;
|
||||
private final DefaultConstructorAllocator allocator;
|
||||
|
||||
private static final class HashSetCreator implements InstanceCreator<HashSet<?>> {
|
||||
public HashSet<?> createInstance(Type type) {
|
||||
return new HashSet<Object>();
|
||||
public DefaultConstructorCreator(Class<? extends T> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<Map<?, ?>>,
|
||||
JsonDeserializer<Map<?, ?>>, InstanceCreator<Map<?, ?>> {
|
||||
|
||||
JsonDeserializer<Map<?, ?>> {
|
||||
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<Map<?, ?>>,
|
||||
return (Map) objectConstructor.construct(mapType);
|
||||
}
|
||||
|
||||
public Map<Object, Object> createInstance(Type type) {
|
||||
return new LinkedHashMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MapTypeAdapter.class.getSimpleName();
|
||||
|
@ -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<Class<?>, Constructor<?>> noArgsConstructorsCache =
|
||||
new LruCache<Class<?>, Constructor<?>>(500);
|
||||
private final ParameterizedTypeHandlerMap<InstanceCreator<?>> 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<MappedObjectConstructor> NULL_VALUE =
|
||||
getNoArgsConstructorUsingReflection(MappedObjectConstructor.class);
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private MappedObjectConstructor() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public MappedObjectConstructor(
|
||||
ParameterizedTypeHandlerMap<InstanceCreator<?>> 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> T constructWithNoArgConstructor(Type typeOfT) {
|
||||
private <T> T constructWithAllocators(Type typeOfT) {
|
||||
try {
|
||||
Class<T> clazz = (Class<T>) Types.getRawType(typeOfT);
|
||||
Constructor<T> 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 <T> Constructor<T> getNoArgsConstructor(Class<T> clazz) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Constructor<T> constructor = (Constructor<T>)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 <T> Constructor<T> getNoArgsConstructorUsingReflection(Class<T> clazz) {
|
||||
try {
|
||||
Constructor<T> constructor = clazz.getDeclaredConstructor();
|
||||
constructor.setAccessible(true);
|
||||
return constructor;
|
||||
} catch (Exception e) {
|
||||
return (Constructor<T>) NULL_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return instanceCreatorMap.toString();
|
||||
|
@ -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<Object> arrayList = allocator.newInstance(ArrayList.class);
|
||||
assertTrue(arrayList.isEmpty());
|
||||
assertInCache(ArrayList.class);
|
||||
|
||||
LinkedList<Object> 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
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user