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.lang.reflect.Constructor;
20 import java.lang.reflect.InvocationTargetException;
21 import java.lang.reflect.ParameterizedType;
22 import java.lang.reflect.Type;
23 import java.util.ArrayDeque;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.EnumSet;
27 import java.util.LinkedHashMap;
28 import java.util.LinkedHashSet;
29 import java.util.Map;
30 import java.util.Queue;
31 import java.util.Set;
32 import java.util.SortedMap;
33 import java.util.SortedSet;
34 import java.util.TreeMap;
35 import java.util.TreeSet;
36 import java.util.concurrent.ConcurrentHashMap;
37 import java.util.concurrent.ConcurrentMap;
38 import java.util.concurrent.ConcurrentNavigableMap;
39 import java.util.concurrent.ConcurrentSkipListMap;
40
41 import com.google.gson.InstanceCreator;
42 import com.google.gson.JsonIOException;
43 import com.google.gson.internal.reflect.ReflectionAccessor;
44 import com.google.gson.reflect.TypeToken;
45
46 /**
47  * Returns a function that can construct an instance of a requested type.
48  */

49 public final class ConstructorConstructor {
50   private final Map<Type, InstanceCreator<?>> instanceCreators;
51   private final ReflectionAccessor accessor = ReflectionAccessor.getInstance();
52
53   public ConstructorConstructor(Map<Type, InstanceCreator<?>> instanceCreators) {
54     this.instanceCreators = instanceCreators;
55   }
56
57   public <T> ObjectConstructor<T> get(TypeToken<T> typeToken) {
58     final Type type = typeToken.getType();
59     final Class<? super T> rawType = typeToken.getRawType();
60
61     // first try an instance creator
62
63     @SuppressWarnings("unchecked"// types must agree
64     final InstanceCreator<T> typeCreator = (InstanceCreator<T>) instanceCreators.get(type);
65     if (typeCreator != null) {
66       return new ObjectConstructor<T>() {
67         @Override public T construct() {
68           return typeCreator.createInstance(type);
69         }
70       };
71     }
72
73     // Next try raw type match for instance creators
74     @SuppressWarnings("unchecked"// types must agree
75     final InstanceCreator<T> rawTypeCreator =
76         (InstanceCreator<T>) instanceCreators.get(rawType);
77     if (rawTypeCreator != null) {
78       return new ObjectConstructor<T>() {
79         @Override public T construct() {
80           return rawTypeCreator.createInstance(type);
81         }
82       };
83     }
84
85     ObjectConstructor<T> defaultConstructor = newDefaultConstructor(rawType);
86     if (defaultConstructor != null) {
87       return defaultConstructor;
88     }
89
90     ObjectConstructor<T> defaultImplementation = newDefaultImplementationConstructor(type, rawType);
91     if (defaultImplementation != null) {
92       return defaultImplementation;
93     }
94
95     // finally try unsafe
96     return newUnsafeAllocator(type, rawType);
97   }
98
99   private <T> ObjectConstructor<T> newDefaultConstructor(Class<? super T> rawType) {
100     try {
101       final Constructor<? super T> constructor = rawType.getDeclaredConstructor();
102       if (!constructor.isAccessible()) {
103         accessor.makeAccessible(constructor);
104       }
105       return new ObjectConstructor<T>() {
106         @SuppressWarnings("unchecked"// T is the same raw type as is requested
107         @Override public T construct() {
108           try {
109             Object[] args = null;
110             return (T) constructor.newInstance(args);
111           } catch (InstantiationException e) {
112             // TODO: JsonParseException ?
113             throw new RuntimeException("Failed to invoke " + constructor + " with no args", e);
114           } catch (InvocationTargetException e) {
115             // TODO: don't wrap if cause is unchecked!
116             // TODO: JsonParseException ?
117             throw new RuntimeException("Failed to invoke " + constructor + " with no args",
118                 e.getTargetException());
119           } catch (IllegalAccessException e) {
120             throw new AssertionError(e);
121           }
122         }
123       };
124     } catch (NoSuchMethodException e) {
125       return null;
126     }
127   }
128
129   /**
130    * Constructors for common interface types like Map and List and their
131    * subtypes.
132    */

133   @SuppressWarnings("unchecked"// use runtime checks to guarantee that 'T' is what it is
134   private <T> ObjectConstructor<T> newDefaultImplementationConstructor(
135       final Type type, Class<? super T> rawType) {
136     if (Collection.class.isAssignableFrom(rawType)) {
137       if (SortedSet.class.isAssignableFrom(rawType)) {
138         return new ObjectConstructor<T>() {
139           @Override public T construct() {
140             return (T) new TreeSet<Object>();
141           }
142         };
143       } else if (EnumSet.class.isAssignableFrom(rawType)) {
144         return new ObjectConstructor<T>() {
145           @SuppressWarnings("rawtypes")
146           @Override public T construct() {
147             if (type instanceof ParameterizedType) {
148               Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
149               if (elementType instanceof Class) {
150                 return (T) EnumSet.noneOf((Class)elementType);
151               } else {
152                 throw new JsonIOException("Invalid EnumSet type: " + type.toString());
153               }
154             } else {
155               throw new JsonIOException("Invalid EnumSet type: " + type.toString());
156             }
157           }
158         };
159       } else if (Set.class.isAssignableFrom(rawType)) {
160         return new ObjectConstructor<T>() {
161           @Override public T construct() {
162             return (T) new LinkedHashSet<Object>();
163           }
164         };
165       } else if (Queue.class.isAssignableFrom(rawType)) {
166         return new ObjectConstructor<T>() {
167           @Override public T construct() {
168             return (T) new ArrayDeque<Object>();
169           }
170         };
171       } else {
172         return new ObjectConstructor<T>() {
173           @Override public T construct() {
174             return (T) new ArrayList<Object>();
175           }
176         };
177       }
178     }
179
180     if (Map.class.isAssignableFrom(rawType)) {
181       if (ConcurrentNavigableMap.class.isAssignableFrom(rawType)) {
182         return new ObjectConstructor<T>() {
183           @Override public T construct() {
184             return (T) new ConcurrentSkipListMap<Object, Object>();
185           }
186         };
187       } else if (ConcurrentMap.class.isAssignableFrom(rawType)) {
188         return new ObjectConstructor<T>() {
189           @Override public T construct() {
190             return (T) new ConcurrentHashMap<Object, Object>();
191           }
192         };
193       } else if (SortedMap.class.isAssignableFrom(rawType)) {
194         return new ObjectConstructor<T>() {
195           @Override public T construct() {
196             return (T) new TreeMap<Object, Object>();
197           }
198         };
199       } else if (type instanceof ParameterizedType && !(String.class.isAssignableFrom(
200           TypeToken.get(((ParameterizedType) type).getActualTypeArguments()[0]).getRawType()))) {
201         return new ObjectConstructor<T>() {
202           @Override public T construct() {
203             return (T) new LinkedHashMap<Object, Object>();
204           }
205         };
206       } else {
207         return new ObjectConstructor<T>() {
208           @Override public T construct() {
209             return (T) new LinkedTreeMap<String, Object>();
210           }
211         };
212       }
213     }
214
215     return null;
216   }
217
218   private <T> ObjectConstructor<T> newUnsafeAllocator(
219       final Type type, final Class<? super T> rawType) {
220     return new ObjectConstructor<T>() {
221       private final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create();
222       @SuppressWarnings("unchecked")
223       @Override public T construct() {
224         try {
225           Object newInstance = unsafeAllocator.newInstance(rawType);
226           return (T) newInstance;
227         } catch (Exception e) {
228           throw new RuntimeException(("Unable to invoke no-args constructor for " + type + ". "
229               + "Registering an InstanceCreator with Gson for this type may fix this problem."), e);
230         }
231       }
232     };
233   }
234
235   @Override public String toString() {
236     return instanceCreators.toString();
237   }
238 }
239