1 /*
2 * Grapht, an open source dependency injector.
3 * Copyright 2014-2015 various contributors (see CONTRIBUTORS.txt)
4 * Copyright 2010-2014 Regents of the University of Minnesota
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as
8 * published by the Free Software Foundation; either version 2.1 of the
9 * License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14 * details.
15 *
16 * You should have received a copy of the GNU General Public License along with
17 * this program; if not, write to the Free Software Foundation, Inc., 51
18 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 */
20 package org.grouplens.grapht.util;
21
22 import org.apache.commons.lang3.reflect.TypeUtils;
23
24 import javax.annotation.Nonnull;
25 import javax.inject.Inject;
26 import javax.inject.Provider;
27 import java.lang.annotation.Annotation;
28 import java.lang.reflect.*;
29 import java.util.Map;
30
31 /**
32 * Static helper methods for working with types.
33 *
34 * @author <a href="http://grouplens.org">GroupLens Research</a>
35 * @author <a href="http://grouplens.org">GroupLens Research</a>
36 */
37 public final class Types {
38
39 private static final TypeVariable<?> PROVIDER_TYPE_VAR =Provider.class.getTypeParameters()[0];
40
41 private Types() {}
42
43 private static final Class<?>[] PRIMITIVE_TYPES = {
44 boolean.class, char.class,
45 byte.class, short.class, int.class, long.class,
46 double.class, float.class
47 };
48
49 /**
50 * Create a parameterized type wrapping the given class and type arguments.
51 *
52 * @param type
53 * @param arguments
54 * @return
55 */
56 public static Type parameterizedType(Class<?> type, Type... arguments) {
57 return new ParameterizedTypeImpl(type, arguments);
58 }
59
60 /**
61 * Return the boxed version of the given type if the type is primitive.
62 * Otherwise, if the type is not a primitive the original type is returned.
63 * As an example, int.class is converted to Integer.class, but List.class is
64 * unchanged. This version of box preserves generics.
65 *
66 * @param type The possibly unboxed type
67 * @return The boxed type
68 */
69 public static Type box(Type type) {
70 if (type instanceof Class) {
71 return box((Class<?>) type);
72 } else {
73 return type;
74 }
75 }
76
77 /**
78 * Return the boxed version of the given type if the type is primitive.
79 * Otherwise, if the type is not primitive the original class is returned.
80 *
81 * @param type The possibly unboxed type
82 * @return The boxed type
83 */
84 public static Class<?> box(Class<?> type) {
85 if (int.class.equals(type)) {
86 return Integer.class;
87 } else if (short.class.equals(type)) {
88 return Short.class;
89 } else if (byte.class.equals(type)) {
90 return Byte.class;
91 } else if (long.class.equals(type)) {
92 return Long.class;
93 } else if (boolean.class.equals(type)) {
94 return Boolean.class;
95 } else if (char.class.equals(type)) {
96 return Character.class;
97 } else if (float.class.equals(type)) {
98 return Float.class;
99 } else if (double.class.equals(type)) {
100 return Double.class;
101 } else {
102 return type;
103 }
104 }
105
106 /**
107 * Compute the erasure of a type.
108 *
109 * @param type The type to erase.
110 * @return The class representing the erasure of the type.
111 * @throws IllegalArgumentException if <var>type</var> is unerasable (e.g.
112 * it is a type variable or a wildcard).
113 */
114 public static Class<?> erase(Type type) {
115 if (type instanceof Class) {
116 return (Class<?>) type;
117 } else if (type instanceof ParameterizedType) {
118 ParameterizedType pt = (ParameterizedType) type;
119 Type raw = pt.getRawType();
120 try {
121 return (Class<?>) raw;
122 } catch (ClassCastException e) {
123 throw new RuntimeException("raw type not a Class", e);
124 }
125 } else {
126 throw new IllegalArgumentException();
127 }
128 }
129
130 /**
131 * Return the type distance between the child and parent types. The child type
132 * must be a subtype of the parent. The type distance between a class and itself is 0;
133 * the distance from a class to one of its immediate supertypes (superclass or a directly
134 * implemented interface) is 1; deeper distances are computed recursively.
135 *
136 * @param child The child type
137 * @param parent The parent type
138 * @return The type distance
139 * @throws IllegalArgumentException if {@code child} is not a subtype of {@code parent}.
140 */
141 public static int getTypeDistance(@Nonnull Class<?> child, @Nonnull Class<?> parent) {
142 Preconditions.notNull("child class", child);
143 Preconditions.notNull("parent class", parent);
144
145 if (child.equals(parent)) {
146 // fast-path same-class tests
147 return 0;
148 } else if (!parent.isAssignableFrom(child)) {
149 // if child does not extend from the parent, return -1
150 throw new IllegalArgumentException("child not a subclass of parent");
151 } else if (!parent.isInterface()) {
152 // if the parent is not an interface, we only need to follower superclasses
153 int distance = 0;
154 Class<?> cur = child;
155 while (!cur.equals(parent)) {
156 distance++;
157 cur = cur.getSuperclass();
158 }
159 return distance;
160 } else {
161 // worst case, recursively compute the type
162 // recursion is safe, as types aren't too deep except in crazy-land
163 int minDepth = Integer.MAX_VALUE;
164 Class<?> sup = child.getSuperclass();
165 if (sup != null && parent.isAssignableFrom(sup)) {
166 minDepth = getTypeDistance(sup, parent);
167 }
168 for (Class<?> iface: child.getInterfaces()) {
169 if (parent.isAssignableFrom(iface)) {
170 int d = getTypeDistance(iface, parent);
171 if (d < minDepth) {
172 minDepth = d;
173 }
174 }
175 }
176 // minDepth now holds the depth of the superclass with shallowest depth
177 return minDepth + 1;
178 }
179 }
180
181 /**
182 * Get the type that is provided by a given implementation of
183 * {@link Provider}.
184 *
185 * @param providerClass The provider's class
186 * @return The provided class type
187 * @throws IllegalArgumentException if the class doesn't actually implement
188 * Provider
189 */
190 public static Class<?> getProvidedType(Class<? extends Provider<?>> providerClass) {
191 com.google.common.base.Preconditions.checkArgument(Provider.class.isAssignableFrom(providerClass),
192 "class is not Provider class");
193 Map<TypeVariable<?>, Type> bindings = TypeUtils.getTypeArguments(providerClass, Provider.class);
194 Type boundType = bindings.get(PROVIDER_TYPE_VAR);
195
196 if(boundType == null || boundType instanceof TypeVariable){
197 throw new IllegalArgumentException("Class provided by " + providerClass.getName() + " is generic");
198 }
199 final Class<?> inferredType = TypeUtils.getRawType(bindings.get(PROVIDER_TYPE_VAR), null);
200 try{
201 final Class<?> observedType = providerClass.getMethod("get").getReturnType();
202 if (inferredType != null && inferredType.isAssignableFrom(observedType)) {
203 return observedType;
204 } else {
205 return inferredType;
206 }
207 } catch (NoSuchMethodException e) {
208 throw new IllegalArgumentException("Class does not implement get()", e);
209 }
210 }
211
212 /**
213 * Get the type that is provided by the Provider instance.
214 *
215 * @param provider The provider instance queried
216 * @return The provided class type
217 * @see #getProvidedType(Class)
218 */
219 @SuppressWarnings("unchecked")
220 public static Class<?> getProvidedType(Provider<?> provider) {
221 if (provider instanceof TypedProvider) {
222 return ((TypedProvider) provider).getProvidedType();
223 } else {
224 return getProvidedType((Class<? extends Provider<?>>) provider.getClass());
225 }
226 }
227
228 /**
229 * Return true if the type is not abstract and not an interface, and has
230 * a constructor annotated with {@link Inject} or its only constructor
231 * is the default constructor.
232 *
233 * @param type A class type
234 * @return True if the class type is instantiable
235 */
236 public static boolean isInstantiable(Class<?> type) {
237 if (!Modifier.isAbstract(type.getModifiers()) && !type.isInterface()) {
238 // first check for a constructor annotated with @Inject,
239 // - this doesn't care how many we'll let the injector complain
240 // if there are more than one
241 for (Constructor<?> c: type.getDeclaredConstructors()) {
242 if (c.getAnnotation(Inject.class) != null) {
243 return true;
244 }
245 }
246
247 // check if we only have the public default constructor
248 if (type.getConstructors().length == 1
249 && type.getConstructors()[0].getParameterTypes().length == 0) {
250 return true;
251 }
252 }
253
254 // no constructor available
255 return false;
256 }
257
258 /**
259 * <p>
260 * Return true if the type is not abstract and not an interface. This will
261 * return true essentially when the class "should" have a default
262 * constructor or a constructor annotated with {@link Inject @Inject} to be
263 * used properly.
264 * <p>
265 * As another special rule, if the input type is {@link Void}, false is
266 * returned because for most intents and purposes, it is not instantiable.
267 *
268 * @param type The type to test
269 * @return True if it should be instantiable
270 */
271 public static boolean shouldBeInstantiable(Class<?> type) {
272 return !Modifier.isAbstract(type.getModifiers()) && !type.isInterface() && !Void.class.equals(type);
273 }
274
275 /**
276 * Return true if the array of Annotations contains an Annotation with a
277 * simple name of 'Nullable'. It does not matter which actual Nullable
278 * annotation is present.
279 *
280 * @param annotations Array of annotations, e.g. from a setter or
281 * constructor
282 * @return True if there exists a Nullable annotation in the array
283 */
284 public static boolean hasNullableAnnotation(Annotation[] annotations) {
285 for (Annotation a: annotations) {
286 if (a.annotationType().getSimpleName().equals("Nullable")) {
287 return true;
288 }
289 }
290 return false;
291 }
292
293 /**
294 * Infer a default class loader.
295 * @return A reasonable default class loader.
296 * @deprecated Use {@link org.grouplens.grapht.util.ClassLoaders#inferDefault()} instead.
297 */
298 @Deprecated
299 public static ClassLoader getDefaultClassLoader() {
300 return ClassLoaders.inferDefault();
301 }
302 }