View Javadoc

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 }