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();
270      }
271
272      final ArrayList<Type> al = new ArrayList<>();
273      al.addAll(Arrays.asList(clazz.getGenericInterfaces()));
274      final Type sc = clazz.getGenericSuperclass();
275      if (sc != null) {
276        al.add(sc);
277      }
278
279      final Type[] interfaces = al.toArray(new Type[0]);
280
281      for (final Type genericNameType : interfaces) {
282        if (genericNameType instanceof ParameterizedType) {
283          final ParameterizedType ptype = (ParameterizedType) genericNameType;
284          if (ptype.getRawType().equals(iface)) {
285            final Type t = ptype.getActualTypeArguments()[0];
286            return t;
287          }
288        }
289      }
290      throw new IllegalArgumentException(clazz + " does not directly implement " + iface);
291    } else {
292      throw new UnsupportedOperationException("Do not know how to get interface target of " + type);
293    }
294  }
295
296  /**
297   * @param clazz
298   * @return T if clazz implements {@code Name<T>}, null otherwise
299   * @throws org.apache.reef.tang.exceptions.BindException
300   * If clazz's definition incorrectly uses Name or @NamedParameter
301   */
302  public static Type getNamedParameterTargetOrNull(final Class<?> clazz)
303      throws ClassHierarchyException {
304    final Annotation npAnnotation = clazz.getAnnotation(NamedParameter.class);
305    final boolean hasSuperClass = clazz.getSuperclass() != Object.class;
306
307    boolean isInjectable = false;
308    boolean hasConstructor = false;
309    // TODO Figure out how to properly differentiate between default and
310    // non-default zero-arg constructors?
311    final Constructor<?>[] constructors = clazz.getDeclaredConstructors();
312    if (constructors.length > 1) {
313      hasConstructor = true;
314    }
315    if (constructors.length == 1) {
316      final Constructor<?> c = constructors[0];
317      final Class<?>[] p = c.getParameterTypes();
318      if (p.length > 1) {
319        // Multiple args. Definitely not implicit.
320        hasConstructor = true;
321      } else if (p.length == 1) {
322        // One arg. Could be an inner class, in which case the compiler
323        // included an implicit one parameter constructor that takes the
324        // enclosing type.
325        if (p[0] != clazz.getEnclosingClass()) {
326          hasConstructor = true;
327        }
328      }
329    }
330    for (final Constructor<?> c : constructors) {
331      for (final Annotation a : c.getDeclaredAnnotations()) {
332        if (a instanceof Inject) {
333          isInjectable = true;
334        }
335      }
336    }
337
338    final Class<?>[] allInterfaces = clazz.getInterfaces();
339
340    final boolean hasMultipleInterfaces = allInterfaces.length > 1;
341    boolean implementsName;
342    Type parameterClass = null;
343    try {
344      parameterClass = getInterfaceTarget(Name.class, clazz);
345      implementsName = true;
346    } catch (final IllegalArgumentException e) {
347      implementsName = false;
348    }
349
350    if (npAnnotation == null) {
351      if (implementsName) {
352        throw new ClassHierarchyException("Named parameter " + getFullName(clazz)
353            + " is missing its @NamedParameter annotation.");
354      } else {
355        return null;
356      }
357    } else {
358      if (!implementsName) {
359        throw new ClassHierarchyException("Found illegal @NamedParameter " + getFullName(clazz)
360            + " does not implement Name<?>");
361      }
362      if (hasSuperClass) {
363        throw new ClassHierarchyException("Named parameter " + getFullName(clazz)
364            + " has a superclass other than Object.");
365      }
366      if (hasConstructor || isInjectable) {
367        throw new ClassHierarchyException("Named parameter " + getFullName(clazz) + " has "
368            + (isInjectable ? "an injectable" : "a") + " constructor. "
369            + " Named parameters must not declare any constructors.");
370      }
371      if (hasMultipleInterfaces) {
372        throw new ClassHierarchyException("Named parameter " + getFullName(clazz) + " implements "
373            + "multiple interfaces.  It is only allowed to implement Name<T>");
374      }
375      if (parameterClass == null) {
376        throw new ClassHierarchyException(
377            "Missing type parameter in named parameter declaration.  " + getFullName(clazz)
378                + " implements raw type Name, but must implement"
379                + " generic type Name<T>.");
380      }
381      return parameterClass;
382    }
383  }
384
385  /**
386   * Coerce a Type into a Class.  This strips out any generic paramters, and
387   * resolves wildcards and free parameters to Object.
388   * <p>
389   * Examples:
390   * <pre>{@code
391   * java.util.Set<String> -> java.util.Set
392   * ? extends T -> Object
393   * T -> Object
394   * ? -> Object
395   * }</pre>
396   */
397  public static Class<?> getRawClass(final Type clazz) {
398    if (clazz instanceof Class) {
399      return (Class<?>) clazz;
400    } else if (clazz instanceof ParameterizedType) {
401      return (Class<?>) ((ParameterizedType) clazz).getRawType();
402    } else if (clazz instanceof WildcardType) {
403      return Object.class; // XXX not really correct, but close enough?
404    } else if (clazz instanceof TypeVariable) {
405      return Object.class; // XXX not really correct, but close enough?
406    } else {
407      System.err.println("Can't getRawClass for " + clazz + " of unknown type " + clazz.getClass());
408      throw new IllegalArgumentException("Can't getRawClass for " + clazz + " of unknown type " + clazz.getClass());
409    }
410  }
411
412  /**
413   * Empty private constructor to prohibit instantiation of utility class.
414   */
415  private ReflectionUtilities() {
416  }
417}