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}