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