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 package com.squareup.moshi;
17
18 import com.squareup.moshi.internal.Util;
19 import java.io.ObjectInputStream;
20 import java.io.ObjectStreamClass;
21 import java.lang.reflect.Constructor;
22 import java.lang.reflect.Field;
23 import java.lang.reflect.InvocationTargetException;
24 import java.lang.reflect.Method;
25
26 /**
27  * Magic that creates instances of arbitrary concrete classes. Derived from Gson's UnsafeAllocator
28  * and ConstructorConstructor classes.
29  *
30  * @author Joel Leitch
31  * @author Jesse Wilson
32  */

33 abstract class ClassFactory<T> {
34   abstract T newInstance() throws
35       InvocationTargetException, IllegalAccessException, InstantiationException;
36
37   public static <T> ClassFactory<T> get(final Class<?> rawType) {
38     // Try to find a no-args constructor. May be any visibility including private.
39     try {
40       final Constructor<?> constructor = rawType.getDeclaredConstructor();
41       constructor.setAccessible(true);
42       return new ClassFactory<T>() {
43         @SuppressWarnings("unchecked"// T is the same raw type as is requested
44         @Override public T newInstance() throws IllegalAccessException, InvocationTargetException,
45             InstantiationException {
46           Object[] args = null;
47           return (T) constructor.newInstance(args);
48         }
49         @Override public String toString() {
50           return rawType.getName();
51         }
52       };
53     } catch (NoSuchMethodException ignored) {
54       // No no-args constructor. Fall back to something more magical...
55     }
56
57     // Try the JVM's Unsafe mechanism.
58     // public class Unsafe {
59     //   public Object allocateInstance(Class<?> type);
60     // }
61     try {
62       Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
63       Field f = unsafeClass.getDeclaredField("theUnsafe");
64       f.setAccessible(true);
65       final Object unsafe = f.get(null);
66       final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class);
67       return new ClassFactory<T>() {
68         @SuppressWarnings("unchecked")
69         @Override public T newInstance() throws InvocationTargetException, IllegalAccessException {
70           return (T) allocateInstance.invoke(unsafe, rawType);
71         }
72         @Override public String toString() {
73           return rawType.getName();
74         }
75       };
76     } catch (IllegalAccessException e) {
77       throw new AssertionError();
78     } catch (ClassNotFoundException | NoSuchMethodException | NoSuchFieldException ignored) {
79       // Not the expected version of the Oracle Java library!
80     }
81
82     // Try (post-Gingerbread) Dalvik/libcore's ObjectStreamClass mechanism.
83     // public class ObjectStreamClass {
84     //   private static native int getConstructorId(Class<?> c);
85     //   private static native Object newInstance(Class<?> instantiationClass, int methodId);
86     // }
87     try {
88       Method getConstructorId = ObjectStreamClass.class.getDeclaredMethod(
89           "getConstructorId", Class.class);
90       getConstructorId.setAccessible(true);
91       final int constructorId = (Integer) getConstructorId.invoke(null, Object.class);
92       final Method newInstance = ObjectStreamClass.class.getDeclaredMethod("newInstance",
93           Class.classint.class);
94       newInstance.setAccessible(true);
95       return new ClassFactory<T>() {
96         @SuppressWarnings("unchecked")
97         @Override public T newInstance() throws InvocationTargetException, IllegalAccessException {
98           return (T) newInstance.invoke(null, rawType, constructorId);
99         }
100         @Override public String toString() {
101           return rawType.getName();
102         }
103       };
104     } catch (IllegalAccessException e) {
105       throw new AssertionError();
106     } catch (InvocationTargetException e) {
107       throw Util.rethrowCause(e);
108     } catch (NoSuchMethodException ignored) {
109       // Not the expected version of Dalvik/libcore!
110     }
111
112     // Try (pre-Gingerbread) Dalvik/libcore's ObjectInputStream mechanism.
113     // public class ObjectInputStream {
114     //   private static native Object newInstance(
115     //     Class<?> instantiationClass, Class<?> constructorClass);
116     // }
117     try {
118       final Method newInstance = ObjectInputStream.class.getDeclaredMethod(
119           "newInstance", Class.class, Class.class);
120       newInstance.setAccessible(true);
121       return new ClassFactory<T>() {
122         @SuppressWarnings("unchecked")
123         @Override public T newInstance() throws InvocationTargetException, IllegalAccessException {
124           return (T) newInstance.invoke(null, rawType, Object.class);
125         }
126         @Override public String toString() {
127           return rawType.getName();
128         }
129       };
130     } catch (Exception ignored) {
131     }
132
133     throw new IllegalArgumentException("cannot construct instances of " + rawType.getName());
134   }
135 }
136