diff --git a/gson/src/main/java/com/google/gson/MappedObjectConstructor.java b/gson/src/main/java/com/google/gson/MappedObjectConstructor.java index 0ad8ebb4..a34be7c0 100644 --- a/gson/src/main/java/com/google/gson/MappedObjectConstructor.java +++ b/gson/src/main/java/com/google/gson/MappedObjectConstructor.java @@ -16,11 +16,15 @@ package com.google.gson; -import java.lang.reflect.AccessibleObject; +import sun.misc.Unsafe; + import java.lang.reflect.Array; import java.lang.reflect.Constructor; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Type; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.logging.Level; import java.util.logging.Logger; @@ -36,6 +40,20 @@ import java.util.logging.Logger; */ final class MappedObjectConstructor implements ObjectConstructor { private static final Logger log = Logger.getLogger(MappedObjectConstructor.class.getName()); + private static final Unsafe THE_UNSAFE = AccessController.doPrivileged( + new PrivilegedAction() { + public Unsafe run() { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + return (Unsafe) f.get(null); + } catch (NoSuchFieldException e) { + throw new Error(); + } catch (IllegalAccessException e) { + throw new Error(); + } + } + }); private final ParameterizedTypeHandlerMap> instanceCreatorMap; @@ -57,12 +75,13 @@ final class MappedObjectConstructor implements ObjectConstructor { return Array.newInstance(Types.getRawType(type), length); } + @SuppressWarnings({"unchecked", "cast"}) private T constructWithNoArgConstructor(Type typeOfT) { try { - Constructor constructor = getNoArgsConstructor(typeOfT); + Class clazz = (Class) Types.getRawType(typeOfT); + Constructor constructor = getNoArgsConstructor(clazz); if (constructor == null) { - throw new RuntimeException(("No-args constructor for " + typeOfT + " does not exist. " - + "Register an InstanceCreator with Gson for this type to fix this problem.")); + return (T) THE_UNSAFE.allocateInstance(clazz); } return constructor.newInstance(); } catch (InstantiationException e) { @@ -77,17 +96,14 @@ final class MappedObjectConstructor implements ObjectConstructor { } } - @SuppressWarnings({"unchecked", "cast"}) - private Constructor getNoArgsConstructor(Type typeOfT) { - Class clazz = Types.getRawType(typeOfT); - Constructor[] declaredConstructors = (Constructor[]) clazz.getDeclaredConstructors(); - AccessibleObject.setAccessible(declaredConstructors, true); - for (Constructor constructor : declaredConstructors) { - if (constructor.getParameterTypes().length == 0) { - return constructor; - } + private Constructor getNoArgsConstructor(Class clazz) { + try { + Constructor declaredConstructor = clazz.getDeclaredConstructor(); + declaredConstructor.setAccessible(true); + return declaredConstructor; + } catch (Exception e) { + return null; } - return null; } /** diff --git a/gson/src/test/java/com/google/gson/MappedObjectConstructorTest.java b/gson/src/test/java/com/google/gson/MappedObjectConstructorTest.java new file mode 100644 index 00000000..81e918a6 --- /dev/null +++ b/gson/src/test/java/com/google/gson/MappedObjectConstructorTest.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.common.TestTypes.CrazyLongTypeAdapter; + +import junit.framework.TestCase; + +import java.lang.reflect.Type; + +/** + * Unit tests for the {@link MappedObjectConstructor} class. + * + * @author Joel Leitch + */ +public class MappedObjectConstructorTest extends TestCase { + private ParameterizedTypeHandlerMap> creatorMap; + private MappedObjectConstructor constructor; + + @Override + protected void setUp() throws Exception { + super.setUp(); + creatorMap = new ParameterizedTypeHandlerMap>(); + constructor = new MappedObjectConstructor(creatorMap); + } + + public void testInstanceCreatorTakesTopPrecedence() throws Exception { + creatorMap.register(ObjectWithDefaultConstructor.class, new MyInstanceCreator()); + ObjectWithDefaultConstructor obj = + constructor.construct(ObjectWithDefaultConstructor.class); + assertEquals("instanceCreator", obj.stringValue); + assertEquals(10, obj.intValue); + } + + public void testNoInstanceCreatorInvokesDefaultConstructor() throws Exception { + ObjectWithDefaultConstructor expected = new ObjectWithDefaultConstructor(); + ObjectWithDefaultConstructor obj = + constructor.construct(ObjectWithDefaultConstructor.class); + assertEquals(expected.stringValue, obj.stringValue); + assertEquals(expected.intValue, obj.intValue); + } + + public void testNoDefaultConstructor() throws Exception { + ObjectNoDefaultConstructor obj = constructor.construct(ObjectNoDefaultConstructor.class); + assertNull(obj.stringValue); + assertEquals(0, obj.intValue); + } + + private static class MyInstanceCreator + implements InstanceCreator { + public ObjectWithDefaultConstructor createInstance(Type type) { + return new ObjectWithDefaultConstructor("instanceCreator", 10); + } + } + + private static class ObjectWithDefaultConstructor { + public final String stringValue; + public final int intValue; + + private ObjectWithDefaultConstructor() { + this("default", 5); + } + + public ObjectWithDefaultConstructor(String stringValue, int intValue) { + this.stringValue = stringValue; + this.intValue = intValue; + } + } + + private static class ObjectNoDefaultConstructor extends ObjectWithDefaultConstructor { + public ObjectNoDefaultConstructor(String stringValue, int intValue) { + super(stringValue, intValue); + } + } +}