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}