All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.reef.tang.util.ReflectionUtilities Maven / Gradle / Ivy

There is a newer version: 0.16.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.reef.tang.util;

import org.apache.reef.tang.annotations.Name;
import org.apache.reef.tang.annotations.NamedParameter;
import org.apache.reef.tang.exceptions.ClassHierarchyException;

import javax.inject.Inject;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;

public final class ReflectionUtilities {
  /**
   * This is used to split Java classnames.  Currently, we split on . and on $
   */
  public static final String REGEXP = "[\\.\\$]";

  /**
   * A map from numeric classes to the number of bits used by their representations.
   */
  private static final Map, Integer> SIZEOF = new HashMap<>();

  static {
    SIZEOF.put(Byte.class, Byte.SIZE);
    SIZEOF.put(Short.class, Short.SIZE);
    SIZEOF.put(Integer.class, Integer.SIZE);
    SIZEOF.put(Long.class, Long.SIZE);
    SIZEOF.put(Float.class, Float.SIZE);
    SIZEOF.put(Double.class, Double.SIZE);
  }

  /**
   * Given a primitive type, return its boxed representation.
   * 

* Examples: *

{@code
   * boxClass(int.class) -> Integer.class
   * boxClass(String.class) -> String.class
   * }
* @param c The class to be boxed. * @return The boxed version of c, or c if it is not a primitive type. */ public static Class boxClass(final Class c) { if (c.isPrimitive() && c != Class.class) { if (c == boolean.class) { return Boolean.class; } else if (c == byte.class) { return Byte.class; } else if (c == char.class) { return Character.class; } else if (c == short.class) { return Short.class; } else if (c == int.class) { return Integer.class; } else if (c == long.class) { return Long.class; } else if (c == float.class) { return Float.class; } else if (c == double.class) { return Double.class; } else if (c == void.class) { return Void.class; } else { throw new UnsupportedOperationException( "Encountered unknown primitive type!"); } } else { return c; } } /** * Given a Type, return all of the classes it extends and interfaces it implements (including the class itself). *

* Examples: *

{@code
   * Integer.class -> {Integer.class, Number.class, Object.class}
   * T -> Object
   * ? -> Object
   * HashSet -> {HashSet, Set, Collection, Object}
   * FooEventHandler -> {FooEventHandler, EventHandler, Object}
   * }
*/ public static Iterable classAndAncestors(final Type c) { final List workQueue = new ArrayList<>(); Type clazz = c; workQueue.add(clazz); if (getRawClass(clazz).isInterface()) { workQueue.add(Object.class); } for (int i = 0; i < workQueue.size(); i++) { clazz = workQueue.get(i); if (clazz instanceof Class) { final Class clz = (Class) clazz; final Type sc = clz.getSuperclass(); if (sc != null) { workQueue.add(sc); //c.getSuperclass()); } workQueue.addAll(Arrays.asList(clz.getGenericInterfaces())); } else if (clazz instanceof ParameterizedType) { final ParameterizedType pt = (ParameterizedType) clazz; final Class rawPt = (Class) pt.getRawType(); final Type sc = rawPt.getSuperclass(); // workQueue.add(pt); // workQueue.add(rawPt); if (sc != null) { workQueue.add(sc); } workQueue.addAll(Arrays.asList(rawPt.getGenericInterfaces())); } else if (clazz instanceof WildcardType) { workQueue.add(Object.class); // XXX not really correct, but close enough? } else if (clazz instanceof TypeVariable) { workQueue.add(Object.class); // XXX not really correct, but close enough? } else { throw new RuntimeException(clazz.getClass() + " " + clazz + " is of unknown type!"); } } return workQueue; } /** * Check to see if one class can be coerced into another. A class is * coercable to another if, once both are boxed, the target class is a * superclass or implemented interface of the source class. *

* If both classes are numeric types, then this method returns true iff * the conversion will not result in a loss of precision. *

* TODO: Float and double are currently coercible to int and long. This is a bug. */ public static boolean isCoercable(final Class to, final Class from) { final Class boxedTo = boxClass(to); final Class boxedFrom = boxClass(from); if (Number.class.isAssignableFrom(boxedTo) && Number.class.isAssignableFrom(boxedFrom)) { return SIZEOF.get(boxedFrom) <= SIZEOF.get(boxedTo); } return boxedTo.isAssignableFrom(boxedFrom); } /** * Lookup the provided name using the provided classloader. This method * includes special handling for primitive types, which can be looked up * by short name (all other types need to be looked up by long name). * * @throws ClassNotFoundException */ public static Class classForName(final String name, final ClassLoader loader) throws ClassNotFoundException { if (name.startsWith("[")) { throw new UnsupportedOperationException("No support for arrays, etc. Name was: " + name); } else if (name.equals("boolean")) { return boolean.class; } else if (name.equals("byte")) { return byte.class; } else if (name.equals("char")) { return char.class; } else if (name.equals("short")) { return short.class; } else if (name.equals("int")) { return int.class; } else if (name.equals("long")) { return long.class; } else if (name.equals("float")) { return float.class; } else if (name.equals("double")) { return double.class; } else if (name.equals("void")) { return void.class; } else { return loader.loadClass(name); } } /** * Get the simple name of the class. This varies from the one in Class, in * that it returns "1" for Classes like java.lang.String$1 In contrast, * String.class.getSimpleName() returns "", which is not unique if * java.lang.String$2 exists, causing all sorts of strange bugs. * * @param name * @return */ public static String getSimpleName(final Type name) { final Class clazz = getRawClass(name); final String[] nameArray = clazz.getName().split(REGEXP); final String ret = nameArray[nameArray.length - 1]; if (ret.length() == 0) { throw new IllegalArgumentException("Class " + name + " has zero-length simple name. Can't happen?!?"); } return ret; } /** * Return the full name of the raw type of the provided Type. *

* Examples: *

{@code
   * java.lang.String.class -> "java.lang.String"
   * Set -> "java.util.Set"  // such types can occur as constructor arguments, for example
   * }
* @param name * @return */ public static String getFullName(final Type name) { return getRawClass(name).getName(); } /** * Return the full name of the provided field. This will be globally * unique. Following Java semantics, the full name will have all the * generic parameters stripped out of it. *

* Example: *

{@code
   * Set { int size; } -> java.util.Set.size
   * }
*/ public static String getFullName(final Field f) { return getFullName(f.getDeclaringClass()) + "." + f.getName(); } /** * This method takes a class called clazz that *directly* implements a generic interface or generic class, iface. * Iface should take a single parameter, which this method will return. *

* TODO This is only tested for interfaces, and the type parameters associated with method arguments. * TODO Not sure what we should do in the face of deeply nested generics (eg: {@code Set}) * TODO Recurse up the class hierarchy in case there are intermediate interfaces * * @param iface A generic interface; we're looking up it's first (and only) parameter. * @param type A type that is more specific than clazz, or clazz if no such type is available. * @return The class implemented by the interface, or null(?) if the instantiation was not generic. * @throws IllegalArgumentException if clazz does not directly implement iface. */ public static Type getInterfaceTarget(final Class iface, final Type type) throws IllegalArgumentException { if (type instanceof ParameterizedType) { final ParameterizedType pt = (ParameterizedType) type; if (iface.isAssignableFrom((Class) pt.getRawType())) { final Type t = pt.getActualTypeArguments()[0]; return t; } else { throw new IllegalArgumentException("Parameterized type " + type + " does not extend " + iface); } } else if (type instanceof Class) { final Class clazz = (Class) type; if (!clazz.equals(type)) { throw new IllegalArgumentException("The clazz is " + clazz + " and the type is " + type + ". They must be equal to each other."); } final ArrayList al = new ArrayList<>(); al.addAll(Arrays.asList(clazz.getGenericInterfaces())); final Type sc = clazz.getGenericSuperclass(); if (sc != null) { al.add(sc); } final Type[] interfaces = al.toArray(new Type[0]); for (final Type genericNameType : interfaces) { if (genericNameType instanceof ParameterizedType) { final ParameterizedType ptype = (ParameterizedType) genericNameType; if (ptype.getRawType().equals(iface)) { final Type t = ptype.getActualTypeArguments()[0]; return t; } } } throw new IllegalArgumentException(clazz + " does not directly implement " + iface); } else { throw new UnsupportedOperationException("Do not know how to get interface target of " + type); } } /** * @param clazz * @return T if clazz implements {@code Name}, null otherwise * @throws org.apache.reef.tang.exceptions.BindException * If clazz's definition incorrectly uses Name or @NamedParameter */ public static Type getNamedParameterTargetOrNull(final Class clazz) throws ClassHierarchyException { final Annotation npAnnotation = clazz.getAnnotation(NamedParameter.class); final boolean hasSuperClass = clazz.getSuperclass() != Object.class; boolean isInjectable = false; boolean hasConstructor = false; // TODO Figure out how to properly differentiate between default and // non-default zero-arg constructors? final Constructor[] constructors = clazz.getDeclaredConstructors(); if (constructors.length > 1) { hasConstructor = true; } if (constructors.length == 1) { final Constructor c = constructors[0]; final Class[] p = c.getParameterTypes(); if (p.length > 1) { // Multiple args. Definitely not implicit. hasConstructor = true; } else if (p.length == 1) { // One arg. Could be an inner class, in which case the compiler // included an implicit one parameter constructor that takes the // enclosing type. if (p[0] != clazz.getEnclosingClass()) { hasConstructor = true; } } } for (final Constructor c : constructors) { for (final Annotation a : c.getDeclaredAnnotations()) { if (a instanceof Inject) { isInjectable = true; } } } final Class[] allInterfaces = clazz.getInterfaces(); final boolean hasMultipleInterfaces = allInterfaces.length > 1; boolean implementsName; Type parameterClass = null; try { parameterClass = getInterfaceTarget(Name.class, clazz); implementsName = true; } catch (final IllegalArgumentException e) { implementsName = false; } if (npAnnotation == null) { if (implementsName) { throw new ClassHierarchyException("Named parameter " + getFullName(clazz) + " is missing its @NamedParameter annotation."); } else { return null; } } else { if (!implementsName) { throw new ClassHierarchyException("Found illegal @NamedParameter " + getFullName(clazz) + " does not implement Name"); } if (hasSuperClass) { throw new ClassHierarchyException("Named parameter " + getFullName(clazz) + " has a superclass other than Object."); } if (hasConstructor || isInjectable) { throw new ClassHierarchyException("Named parameter " + getFullName(clazz) + " has " + (isInjectable ? "an injectable" : "a") + " constructor. " + " Named parameters must not declare any constructors."); } if (hasMultipleInterfaces) { throw new ClassHierarchyException("Named parameter " + getFullName(clazz) + " implements " + "multiple interfaces. It is only allowed to implement Name"); } if (parameterClass == null) { throw new ClassHierarchyException( "Missing type parameter in named parameter declaration. " + getFullName(clazz) + " implements raw type Name, but must implement" + " generic type Name."); } return parameterClass; } } /** * Coerce a Type into a Class. This strips out any generic paramters, and * resolves wildcards and free parameters to Object. *

* Examples: *

{@code
   * java.util.Set -> java.util.Set
   * ? extends T -> Object
   * T -> Object
   * ? -> Object
   * }
*/ public static Class getRawClass(final Type clazz) { if (clazz instanceof Class) { return (Class) clazz; } else if (clazz instanceof ParameterizedType) { return (Class) ((ParameterizedType) clazz).getRawType(); } else if (clazz instanceof WildcardType) { return Object.class; // XXX not really correct, but close enough? } else if (clazz instanceof TypeVariable) { return Object.class; // XXX not really correct, but close enough? } else { System.err.println("Can't getRawClass for " + clazz + " of unknown type " + clazz.getClass()); throw new IllegalArgumentException("Can't getRawClass for " + clazz + " of unknown type " + clazz.getClass()); } } /** * Empty private constructor to prohibit instantiation of utility class. */ private ReflectionUtilities() { } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy