001 // Copyright 2006-2008 Regents of the University of California. May be used 002 // under the terms of the revised BSD license. See LICENSING for details. 003 package org.joe_e.reflect; 004 005 import java.lang.reflect.Constructor; 006 import java.lang.reflect.Field; 007 import java.lang.reflect.InvocationTargetException; 008 import java.lang.reflect.Member; 009 import java.lang.reflect.Method; 010 import java.lang.reflect.Modifier; 011 import java.util.Arrays; 012 import java.util.Comparator; 013 import java.util.regex.Pattern; 014 015 import org.joe_e.IsJoeE; 016 import org.joe_e.array.PowerlessArray; 017 import org.joe_e.taming.Policy; 018 019 /** 020 * The reflection interface. 021 * <p> 022 * This API provides reflective access to all the public constructors, public 023 * fields and public methods of public Joe-E classes and interfaces. The API 024 * provides no more permission than is provided by static Joe-E program code. 025 * If you can do something with the reflection API, you could also have done 026 * it using static Joe-E code. The only difference is expressivity. 027 * </p> 028 */ 029 public final class Reflection { 030 private Reflection() {} 031 032 static final Pattern UNQUALIFY = 033 Pattern.compile("[^\\(<> ]*\\.([^<> \\.]*)"); 034 /* 035 * Methods for obtaining reflective objects 036 */ 037 038 /** 039 * Gets a public field. 040 * <p> 041 * This method wraps {@link Class#getField}. 042 * </p> 043 * @param type class to search 044 * @param name field name 045 * @return described field 046 * @throws NoSuchFieldException no matching field found 047 */ 048 static public Field field(final Class<?> type, final String name) 049 throws NoSuchFieldException { 050 return type.getField(name); 051 } 052 053 /** 054 * Gets all public fields. 055 * <p> 056 * This method wraps {@link Class#getFields}. 057 * </p> 058 * @param type object type 059 * @return described fields 060 */ 061 static public PowerlessArray<Field> fields(final Class<?> type) { 062 final Field[] fs = type.getFields(); 063 064 // Sort the members to preserve determinism. 065 Arrays.sort(fs, new Comparator<Field>() { 066 public int compare(final Field a, final Field b) { 067 int diff = a.getName().compareTo(b.getName()); 068 if (diff == 0) { 069 diff = a.getDeclaringClass().getName().compareTo( 070 b.getDeclaringClass().getName()); 071 // Class.getName() fine as long as fields belonging to 072 // proxy classes are never safe(). 073 } 074 return diff; 075 } 076 }); 077 078 return PowerlessArray.array(fs); 079 } 080 081 /** 082 * Gets a public constructor. 083 * <p> 084 * This method wraps {@link Class#getConstructor}. 085 * </p> 086 * @param type class to search 087 * @param args each parameter type 088 * @return described constructor 089 * @throws NoSuchMethodException no matching constructor found 090 */ 091 static public <T> Constructor<T> constructor(final Class<T> type, final Class<?>... args) 092 throws NoSuchMethodException { 093 return type.getConstructor(args); 094 } 095 096 097 /** 098 * Gets all declared public constructors. 099 * <p> 100 * This method wraps {@link Class#getConstructors}. 101 * </p> 102 * @param type class to search 103 * @return all public constructors 104 */ 105 // static public <T> PowerlessArray<Constructor<T>> 106 // constructors(final Class<T> type) { 107 // Although more expressive, this form is not used as it would preclude doing 108 // anything with the result without casting it to just PowerlessArray<?> or 109 // suppressing an unchecked cast warning at the point of use. 110 111 static public PowerlessArray<Constructor<?>> 112 constructors(final Class<?> type) { 113 114 final Constructor<?>[] cs = type.getConstructors(); 115 116 // Sort the members to preserve determinism. 117 Arrays.sort(cs, new Comparator<Constructor<?>>() { 118 public int compare(final Constructor<?> a, final Constructor<?> b) { 119 final Class<?>[] pa = a.getParameterTypes(); 120 final Class<?>[] pb = b.getParameterTypes(); 121 int diff = pa.length - pb.length; 122 for (int i = 0; diff == 0 && i < pa.length; ++i) { 123 diff = pa[i].getName().compareTo(pb[i].getName()); 124 // OK since compiled types don't change 125 } 126 return diff; 127 } 128 }); 129 130 return PowerlessArray.array(cs); 131 } 132 133 134 /** 135 * Gets a public method. 136 * <p> 137 * This method wraps {@link Class#getMethod}. 138 * </p> 139 * @param type class to search 140 * @param name method name 141 * @param args each parameter type 142 * @return described method 143 * @throws NoSuchMethodException no matching method found 144 */ 145 static public Method method(final Class<?> type, final String name, 146 final Class<?>... args) 147 throws NoSuchMethodException { 148 return type.getMethod(name, args); 149 } 150 151 /** 152 * Gets all public methods. 153 * <p> 154 * This method wraps {@link Class#getMethods}. 155 * </p> 156 * @param type object type 157 * @return described methods 158 */ 159 static public PowerlessArray<Method> methods(final Class<?> type) { 160 final Method[] ms = type.getMethods(); 161 162 // Sort the members to preserve determinism. 163 Arrays.sort(ms, new Comparator<Method>() { 164 public int compare(final Method a, final Method b) { 165 int diff = a.getName().compareTo(b.getName()); 166 if (diff == 0) { 167 diff = a.getDeclaringClass().getName().compareTo( 168 b.getDeclaringClass().getName()); 169 // Class.getName() fine as long as methods belonging to 170 // proxy classes are never safe(). 171 if (diff == 0) { 172 final Class<?>[] pa = a.getParameterTypes(); 173 final Class<?>[] pb = b.getParameterTypes(); 174 if (pa.length != pb.length) { 175 diff = pa.length - pb.length; 176 } 177 for (int i = 0; diff == 0 && i < pa.length; ++i) { 178 diff = pa[i].getName().compareTo(pb[i].getName()); 179 } 180 // OK since compiled types don't change 181 } 182 } 183 return diff; 184 } 185 }); 186 187 return PowerlessArray.array(ms); 188 } 189 190 /** 191 * Get the name of the entity represented by a <code>Class</code> object, 192 * in the same format as returned by {@link Class#getName()}. This wrapper 193 * exists to avoid exposing the number of proxy interfaces that have been 194 * generated. 195 * @param c the class to get the name of 196 * @return the name of class <code>c</code> 197 * @throws IllegalArgumentException if <code>c</code> is a proxy class 198 */ 199 static public String getName(Class<?> c) { 200 if (java.lang.reflect.Proxy.isProxyClass(c)) { 201 throw new IllegalArgumentException("Can't get the name of a " + 202 "proxy class."); 203 } else { 204 return c.getName(); 205 } 206 } 207 208 /** 209 * Clears the stack trace on an exception. 210 * @param e exception to modify 211 */ 212 static public void clearStackTrace(final Throwable e) { 213 e.setStackTrace(new StackTraceElement[] {}); 214 } 215 216 /** 217 * boot class loader 218 */ 219 // static private final ClassLoader boot = Runnable.class.getClassLoader(); 220 221 /** 222 * Is the given member allowed to be accessed by Joe-E code? 223 * @param member candidate member 224 * @return <code>true</code> if the member may be used by Joe-E code, 225 * else <code>false</code> 226 */ 227 static private boolean safe(final Member member) { 228 final Class<?> declarer = member.getDeclaringClass(); 229 // safe if declared in a Joe-E package 230 final Package pkg = declarer.getPackage(); 231 // getPackage returns null for proxy classes 232 if (pkg != null && pkg.isAnnotationPresent(IsJoeE.class)) { 233 return true; 234 } 235 236 // getName() is the binary name, possibly with $'s 237 StringBuilder sb = new StringBuilder(declarer.getName()); 238 if (member instanceof Field) { 239 sb.append("." + member.getName()); 240 return Policy.fieldEnabled(sb.toString()); 241 } 242 else if (member instanceof Constructor<?>) { 243 String stringForm = member.toString(); 244 String args = stringForm.substring(stringForm.indexOf('('), 245 stringForm.indexOf(')') + 1); 246 sb.append(UNQUALIFY.matcher(args).replaceAll("$1")); 247 return Policy.constructorEnabled(sb.toString()); 248 } else { // member instanceof Method 249 sb.append("." + member.getName()); 250 String stringForm = member.toString(); 251 String args = stringForm.substring(stringForm.indexOf('('), 252 stringForm.indexOf(')') + 1); 253 sb.append(UNQUALIFY.matcher(args).replaceAll("$1")); 254 return Policy.methodEnabled(sb.toString()); 255 } 256 } 257 258 /* 259 * Methods for using reflective objects 260 */ 261 262 /** 263 * Gets the value of a field. 264 * @param field field to access 265 * @param self target object 266 * @return field value 267 * @throws IllegalAccessException <code>field</code> is inaccessible 268 */ 269 static public Object get(final Field field, final Object self) 270 throws IllegalAccessException { 271 if (!Modifier.isPublic(field.getDeclaringClass().getModifiers())) { 272 throw new IllegalAccessException(field.toString()); 273 } 274 if (!safe(field)) {throw new IllegalAccessException(field.toString());} 275 return field.get(self); 276 } 277 278 /** 279 * Sets the value of a field. 280 * @param field field to access 281 * @param self target object 282 * @param value new value 283 * @throws IllegalAccessException <code>field</code> is inaccessible 284 */ 285 static public void set(final Field field, final Object self, 286 final Object value) throws IllegalAccessException { 287 if (!Modifier.isPublic(field.getDeclaringClass().getModifiers())) { 288 throw new IllegalAccessException(field.toString()); 289 } 290 if (!safe(field)) {throw new IllegalAccessException(field.toString());} 291 field.set(self, value); 292 } 293 294 /** 295 * Invokes a reflected constructor. 296 * @param ctor constructor to invoke 297 * @param args each argument 298 * @return constructed object 299 * @throws IllegalAccessException <code>ctor</code> is inaccessible 300 * @throws ClassCastException <code>ctor.newInstance()</code> throws an 301 * <code>IllegalArgumentException</code>, usually due to mismatched types 302 * @throws Exception an exception thrown by the invoked constructor 303 */ 304 static public <T> T construct(final Constructor<T> ctor, final Object... args) 305 throws Exception { 306 if (!Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) { 307 throw new IllegalAccessException(ctor.toString()); 308 } 309 if (!safe(ctor)) { throw new IllegalAccessException(ctor.toString()); } 310 try { 311 return ctor.newInstance(args); 312 } catch (final IllegalArgumentException e) { 313 throw new ClassCastException(e.getMessage()); 314 } catch (final InvocationTargetException e) { 315 final Throwable cause = e.getCause(); 316 if (cause instanceof Error) { 317 throw (Error) cause; 318 } 319 throw (Exception) cause; 320 } 321 } 322 323 /** 324 * Invokes a reflected method. 325 * @param method method to invoke 326 * @param self target object 327 * @param args each argument 328 * @return invocation return 329 * @throws IllegalAccessException <code>method</code> is inaccessible 330 * @throws ClassCastException <code>method.invoke()</code> throws an 331 * <code>IllegalArgumentException</code>, usually due to mismatched types 332 * @throws Exception an exception thrown by the invoked method 333 */ 334 static public Object invoke(final Method method, final Object self, 335 final Object... args) throws Exception { 336 if (!Modifier.isPublic(method.getDeclaringClass().getModifiers())) { 337 throw new IllegalAccessException(method.toString()); 338 } 339 if (!safe(method)) { 340 throw new IllegalAccessException(method.toString()); 341 } 342 try { 343 return method.invoke(self, args); 344 } catch (final IllegalArgumentException e) { 345 throw new ClassCastException(); 346 } catch (final InvocationTargetException e) { 347 final Throwable cause = e.getCause(); 348 if (cause instanceof Error) { 349 throw (Error) cause; 350 } 351 throw (Exception) cause; 352 } 353 } 354 }