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