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    }