1 /*
2  * Copyright (C) 2011 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package com.google.gson.internal;
18
19 import java.io.ObjectInputStream;
20 import java.io.ObjectStreamClass;
21 import java.lang.reflect.Field;
22 import java.lang.reflect.Method;
23 import java.lang.reflect.Modifier;
24
25 /**
26  * Do sneaky things to allocate objects without invoking their constructors.
27  *
28  * @author Joel Leitch
29  * @author Jesse Wilson
30  */

31 public abstract class UnsafeAllocator {
32   public abstract <T> T newInstance(Class<T> c) throws Exception;
33
34   public static UnsafeAllocator create() {
35     // try JVM
36     // public class Unsafe {
37     //   public Object allocateInstance(Class<?> type);
38     // }
39     try {
40       Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
41       Field f = unsafeClass.getDeclaredField("theUnsafe");
42       f.setAccessible(true);
43       final Object unsafe = f.get(null);
44       final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class);
45       return new UnsafeAllocator() {
46         @Override
47         @SuppressWarnings("unchecked")
48         public <T> T newInstance(Class<T> c) throws Exception {
49           assertInstantiable(c);
50           return (T) allocateInstance.invoke(unsafe, c);
51         }
52       };
53     } catch (Exception ignored) {
54     }
55
56     // try dalvikvm, post-gingerbread
57     // public class ObjectStreamClass {
58     //   private static native int getConstructorId(Class<?> c);
59     //   private static native Object newInstance(Class<?> instantiationClass, int methodId);
60     // }
61     try {
62       Method getConstructorId = ObjectStreamClass.class
63           .getDeclaredMethod("getConstructorId", Class.class);
64       getConstructorId.setAccessible(true);
65       final int constructorId = (Integer) getConstructorId.invoke(null, Object.class);
66       final Method newInstance = ObjectStreamClass.class
67           .getDeclaredMethod("newInstance", Class.classint.class);
68       newInstance.setAccessible(true);
69       return new UnsafeAllocator() {
70         @Override
71         @SuppressWarnings("unchecked")
72         public <T> T newInstance(Class<T> c) throws Exception {
73           assertInstantiable(c);
74           return (T) newInstance.invoke(null, c, constructorId);
75         }
76       };
77     } catch (Exception ignored) {
78     }
79
80     // try dalvikvm, pre-gingerbread
81     // public class ObjectInputStream {
82     //   private static native Object newInstance(
83     //     Class<?> instantiationClass, Class<?> constructorClass);
84     // }
85     try {
86       final Method newInstance = ObjectInputStream.class
87           .getDeclaredMethod("newInstance", Class.class, Class.class);
88       newInstance.setAccessible(true);
89       return new UnsafeAllocator() {
90         @Override
91         @SuppressWarnings("unchecked")
92         public <T> T newInstance(Class<T> c) throws Exception {
93           assertInstantiable(c);
94           return (T) newInstance.invoke(null, c, Object.class);
95         }
96       };
97     } catch (Exception ignored) {
98     }
99
100     // give up
101     return new UnsafeAllocator() {
102       @Override
103       public <T> T newInstance(Class<T> c) {
104         throw new UnsupportedOperationException("Cannot allocate " + c);
105       }
106     };
107   }
108
109   /**
110    * Check if the class can be instantiated by unsafe allocator. If the instance has interface or abstract modifiers
111    * throw an {@link java.lang.UnsupportedOperationException}
112    * @param c instance of the class to be checked
113    */

114   static void assertInstantiable(Class<?> c) {
115     int modifiers = c.getModifiers();
116     if (Modifier.isInterface(modifiers)) {
117       throw new UnsupportedOperationException("Interface can't be instantiated! Interface name: " + c.getName());
118     }
119     if (Modifier.isAbstract(modifiers)) {
120       throw new UnsupportedOperationException("Abstract class can't be instantiated! Class name: " + c.getName());
121     }
122   }
123 }
124