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 }