This project has retired. For details please refer to its Attic page.
Source code
001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.reef.tang.util;
020
021import org.apache.reef.tang.annotations.Name;
022import org.apache.reef.tang.annotations.NamedParameter;
023import org.apache.reef.tang.exceptions.BindException;
024import org.apache.reef.tang.exceptions.ClassHierarchyException;
025
026import javax.inject.Inject;
027import java.lang.annotation.Annotation;
028import java.lang.reflect.*;
029import java.util.*;
030
031public class ReflectionUtilities {
032  /**
033   * This is used to split Java classnames.  Currently, we split on . and on $
034   */
035  public final static String regexp = "[\\.\\$]";
036  /**
037   * A map from numeric classes to the number of bits used by their representations.
038   */
039  private final static Map<Class<?>, Integer> sizeof = new HashMap<>();
040
041  static {
042    sizeof.put(Byte.class, Byte.SIZE);
043    sizeof.put(Short.class, Short.SIZE);
044    sizeof.put(Integer.class, Integer.SIZE);
045    sizeof.put(Long.class, Long.SIZE);
046    sizeof.put(Float.class, Float.SIZE);
047    sizeof.put(Double.class, Double.SIZE);
048  }
049
050  /**
051   * Given a primitive type, return its boxed representation.
052   * <p/>
053   * Examples:
054   * boxClass(int.class) -> Integer.class
055   * boxClass(String.class) -> String.class
056   *
057   * @param c The class to be boxed.
058   * @return The boxed version of c, or c if it is not a primitive type.
059   */
060  public static Class<?> boxClass(Class<?> c) {
061    if (c.isPrimitive() && c != Class.class) {
062      if (c == boolean.class) {
063        return Boolean.class;
064      } else if (c == byte.class) {
065        return Byte.class;
066      } else if (c == char.class) {
067        return Character.class;
068      } else if (c == short.class) {
069        return Short.class;
070      } else if (c == int.class) {
071        return Integer.class;
072      } else if (c == long.class) {
073        return Long.class;
074      } else if (c == float.class) {
075        return Float.class;
076      } else if (c == double.class) {
077        return Double.class;
078      } else if (c == void.class) {
079        return Void.class;
080      } else {
081        throw new UnsupportedOperationException(
082            "Encountered unknown primitive type!");
083      }
084    } else {
085      return c;
086    }
087  }
088
089  /**
090   * Given a Type, return all of the classes it extends and interfaces it implements (including the class itself).
091   * <p/>
092   * Examples:
093   * Integer.class -> {Integer.class, Number.class, Object.class}
094   * T -> Object
095   * ? -> Object
096   * HashSet<T> -> {HashSet<T>, Set<T>, Collection<T>, Object}
097   * FooEventHandler -> {FooEventHandler, EventHandler<Foo>, Object}
098   */
099  public static Iterable<Type> classAndAncestors(Type c) {
100    List<Type> workQueue = new ArrayList<>();
101
102    workQueue.add(c);
103    if (getRawClass(c).isInterface()) {
104      workQueue.add(Object.class);
105    }
106    for (int i = 0; i < workQueue.size(); i++) {
107      c = workQueue.get(i);
108
109      if (c instanceof Class) {
110        Class<?> clz = (Class<?>) c;
111        final Type sc = clz.getSuperclass();
112        if (sc != null) workQueue.add(sc); //c.getSuperclass());
113        workQueue.addAll(Arrays.asList(clz.getGenericInterfaces()));
114      } else if (c instanceof ParameterizedType) {
115        ParameterizedType pt = (ParameterizedType) c;
116        Class<?> rawPt = (Class<?>) pt.getRawType();
117        final Type sc = rawPt.getSuperclass();
118//        workQueue.add(pt);
119//        workQueue.add(rawPt);
120        if (sc != null) workQueue.add(sc);
121        workQueue.addAll(Arrays.asList(rawPt.getGenericInterfaces()));
122      } else if (c instanceof WildcardType) {
123        workQueue.add(Object.class); // XXX not really correct, but close enough?
124      } else if (c instanceof TypeVariable) {
125        workQueue.add(Object.class); // XXX not really correct, but close enough?
126      } else {
127        throw new RuntimeException(c.getClass() + " " + c + " is of unknown type!");
128      }
129    }
130    return workQueue;
131  }
132
133  /**
134   * Check to see if one class can be coerced into another.  A class is
135   * coercable to another if, once both are boxed, the target class is a
136   * superclass or implemented interface of the source class.
137   * <p/>
138   * If both classes are numeric types, then this method returns true iff
139   * the conversion will not result in a loss of precision.
140   * <p/>
141   * TODO: Float and double are currently coercible to int and long.  This is a bug.
142   */
143  public static boolean isCoercable(Class<?> to, Class<?> from) {
144    to = boxClass(to);
145    from = boxClass(from);
146    if (Number.class.isAssignableFrom(to)
147        && Number.class.isAssignableFrom(from)) {
148      return sizeof.get(from) <= sizeof.get(to);
149    }
150    return to.isAssignableFrom(from);
151  }
152
153  /**
154   * Lookup the provided name using the provided classloader. This method
155   * includes special handling for primitive types, which can be looked up
156   * by short name (all other types need to be looked up by long name).
157   *
158   * @throws ClassNotFoundException
159   */
160  public static Class<?> classForName(String name, ClassLoader loader)
161      throws ClassNotFoundException {
162    if (name.startsWith("[")) {
163      throw new UnsupportedOperationException("No support for arrays, etc.  Name was: " + name);
164    } else if (name.equals("boolean")) {
165      return boolean.class;
166    } else if (name.equals("byte")) {
167      return byte.class;
168    } else if (name.equals("char")) {
169      return char.class;
170    } else if (name.equals("short")) {
171      return short.class;
172    } else if (name.equals("int")) {
173      return int.class;
174    } else if (name.equals("long")) {
175      return long.class;
176    } else if (name.equals("float")) {
177      return float.class;
178    } else if (name.equals("double")) {
179      return double.class;
180    } else if (name.equals("void")) {
181      return void.class;
182    } else {
183      return loader.loadClass(name);
184    }
185  }
186
187  /**
188   * Get the simple name of the class. This varies from the one in Class, in
189   * that it returns "1" for Classes like java.lang.String$1 In contrast,
190   * String.class.getSimpleName() returns "", which is not unique if
191   * java.lang.String$2 exists, causing all sorts of strange bugs.
192   *
193   * @param name
194   * @return
195   */
196  public static String getSimpleName(Type name) {
197    final Class<?> clazz = getRawClass(name);
198    final String[] nameArray = clazz.getName().split(regexp);
199    final String ret = nameArray[nameArray.length - 1];
200    if (ret.length() == 0) {
201      throw new IllegalArgumentException("Class " + name + " has zero-length simple name.  Can't happen?!?");
202    }
203    return ret;
204  }
205
206  /**
207   * Return the full name of the raw type of the provided Type.
208   * <p/>
209   * Examples:
210   * <p/>
211   * java.lang.String.class -> "java.lang.String"
212   * Set<String> -> "java.util.Set"  // such types can occur as constructor arguments, for example
213   *
214   * @param name
215   * @return
216   */
217  public static String getFullName(Type name) {
218    return getRawClass(name).getName();
219  }
220
221  /**
222   * Return the full name of the provided field.  This will be globally
223   * unique.  Following Java semantics, the full name will have all the
224   * generic parameters stripped out of it.
225   * <p/>
226   * Example:
227   * <p/>
228   * Set<X> { int size; } -> java.util.Set.size
229   */
230  public static String getFullName(Field f) {
231    return getFullName(f.getDeclaringClass()) + "." + f.getName();
232  }
233
234  /**
235   * This method takes a class called clazz that *directly* implements a generic interface or generic class, iface.
236   * Iface should take a single parameter, which this method will return.
237   * <p/>
238   * TODO This is only tested for interfaces, and the type parameters associated with method arguments.
239   * TODO Not sure what we should do in the face of deeply nested generics (eg: Set<Set<String>)
240   * TODO Recurse up the class hierarchy in case there are intermediate interfaces
241   *
242   * @param iface A generic interface; we're looking up it's first (and only) parameter.
243   * @param type  A type that is more specific than clazz, or clazz if no such type is available.
244   * @return The class implemented by the interface, or null(?) if the instantiation was not generic.
245   * @throws IllegalArgumentException if clazz does not directly implement iface.
246   */
247  static public Type getInterfaceTarget(final Class<?> iface, final Type type) throws IllegalArgumentException {
248    if (type instanceof ParameterizedType) {
249      final ParameterizedType pt = (ParameterizedType) type;
250      if (iface.isAssignableFrom((Class<?>) pt.getRawType())) {
251        Type t = pt.getActualTypeArguments()[0];
252        return t;
253      } else {
254        throw new IllegalArgumentException("Parameterized type " + type + " does not extend " + iface);
255      }
256    } else if (type instanceof Class) {
257      final Class<?> clazz = (Class<?>) type;
258
259      if (!clazz.equals(type)) {
260        throw new IllegalArgumentException();
261      }
262
263      ArrayList<Type> al = new ArrayList<>();
264      al.addAll(Arrays.asList(clazz.getGenericInterfaces()));
265      Type sc = clazz.getGenericSuperclass();
266      if (sc != null) al.add(sc);
267
268      final Type[] interfaces = al.toArray(new Type[0]);
269
270      for (Type genericNameType : interfaces) {
271        if (genericNameType instanceof ParameterizedType) {
272          ParameterizedType ptype = (ParameterizedType) genericNameType;
273          if (ptype.getRawType().equals(iface)) {
274            Type t = ptype.getActualTypeArguments()[0];
275            return t;
276          }
277        }
278      }
279      throw new IllegalArgumentException(clazz + " does not directly implement " + iface);
280    } else {
281      throw new UnsupportedOperationException("Do not know how to get interface target of " + type);
282    }
283  }
284
285  /**
286   * @param clazz
287   * @return T if clazz implements Name<T>, null otherwise
288   * @throws BindException If clazz's definition incorrectly uses Name or @NamedParameter
289   */
290  static public Type getNamedParameterTargetOrNull(Class<?> clazz)
291      throws ClassHierarchyException {
292    Annotation npAnnotation = clazz.getAnnotation(NamedParameter.class);
293    boolean hasSuperClass = (clazz.getSuperclass() != Object.class);
294
295    boolean isInjectable = false;
296    boolean hasConstructor = false;
297    // TODO Figure out how to properly differentiate between default and
298    // non-default zero-arg constructors?
299    Constructor<?>[] constructors = clazz.getDeclaredConstructors();
300    if (constructors.length > 1) {
301      hasConstructor = true;
302    }
303    if (constructors.length == 1) {
304      Constructor<?> c = constructors[0];
305      Class<?>[] p = c.getParameterTypes();
306      if (p.length > 1) {
307        // Multiple args. Definitely not implicit.
308        hasConstructor = true;
309      } else if (p.length == 1) {
310        // One arg. Could be an inner class, in which case the compiler
311        // included an implicit one parameter constructor that takes the
312        // enclosing type.
313        if (p[0] != clazz.getEnclosingClass()) {
314          hasConstructor = true;
315        }
316      }
317    }
318    for (Constructor<?> c : constructors) {
319      for (Annotation a : c.getDeclaredAnnotations()) {
320        if (a instanceof Inject) {
321          isInjectable = true;
322        }
323      }
324    }
325
326    Class<?>[] allInterfaces = clazz.getInterfaces();
327
328    boolean hasMultipleInterfaces = (allInterfaces.length > 1);
329    boolean implementsName;
330    Type parameterClass = null;
331    try {
332      parameterClass = getInterfaceTarget(Name.class, clazz);
333      implementsName = true;
334    } catch (IllegalArgumentException e) {
335      implementsName = false;
336    }
337
338    if (npAnnotation == null) {
339      if (implementsName) {
340        throw new ClassHierarchyException("Named parameter " + getFullName(clazz)
341            + " is missing its @NamedParameter annotation.");
342      } else {
343        return null;
344      }
345    } else {
346      if (!implementsName) {
347        throw new ClassHierarchyException("Found illegal @NamedParameter " + getFullName(clazz)
348            + " does not implement Name<?>");
349      }
350      if (hasSuperClass) {
351        throw new ClassHierarchyException("Named parameter " + getFullName(clazz)
352            + " has a superclass other than Object.");
353      }
354      if (hasConstructor || isInjectable) {
355        throw new ClassHierarchyException("Named parameter " + getFullName(clazz) + " has "
356            + (isInjectable ? "an injectable" : "a") + " constructor. "
357            + " Named parameters must not declare any constructors.");
358      }
359      if (hasMultipleInterfaces) {
360        throw new ClassHierarchyException("Named parameter " + getFullName(clazz) + " implements "
361            + "multiple interfaces.  It is only allowed to implement Name<T>");
362      }
363      if (parameterClass == null) {
364        throw new ClassHierarchyException(
365            "Missing type parameter in named parameter declaration.  " + getFullName(clazz)
366                + " implements raw type Name, but must implement"
367                + " generic type Name<T>.");
368      }
369      return parameterClass;
370    }
371  }
372
373  /**
374   * Coerce a Type into a Class.  This strips out any generic paramters, and
375   * resolves wildcards and free parameters to Object.
376   * <p/>
377   * Examples:
378   * java.util.Set<String> -> java.util.Set
379   * ? extends T -> Object
380   * T -> Object
381   * ? -> Object
382   */
383  public static Class<?> getRawClass(Type clazz) {
384    if (clazz instanceof Class) {
385      return (Class<?>) clazz;
386    } else if (clazz instanceof ParameterizedType) {
387      return (Class<?>) ((ParameterizedType) clazz).getRawType();
388    } else if (clazz instanceof WildcardType) {
389      return Object.class; // XXX not really correct, but close enough?
390    } else if (clazz instanceof TypeVariable) {
391      return Object.class; // XXX not really correct, but close enough?
392    } else {
393      System.err.println("Can't getRawClass for " + clazz + " of unknown type " + clazz.getClass());
394      throw new IllegalArgumentException("Can't getRawClass for " + clazz + " of unknown type " + clazz.getClass());
395    }
396  }
397}