001    package groovy.inspect;
002    
003    import groovy.lang.GroovyObject;
004    import groovy.lang.MetaClass;
005    import groovy.lang.MetaMethod;
006    import groovy.lang.PropertyValue;
007    
008    import java.lang.reflect.Modifier;
009    import java.lang.reflect.Method;
010    import java.lang.reflect.Field;
011    import java.lang.reflect.Constructor;
012    import java.util.*;
013    
014    import org.codehaus.groovy.runtime.InvokerHelper;
015    import org.codehaus.groovy.runtime.DefaultGroovyMethods;
016    
017    /**
018     * The Inspector provides a unified access to an object's
019     * information that can be determined by introspection.
020     *
021     * @author Dierk Koenig
022     */
023    public class Inspector {
024        protected Object objectUnderInspection = null;
025    
026        // Indexes to retrieve Class Property information
027        public static final int CLASS_PACKAGE_IDX       = 0;
028        public static final int CLASS_CLASS_IDX         = 1;
029        public static final int CLASS_INTERFACE_IDX     = 2;
030        public static final int CLASS_SUPERCLASS_IDX    = 3;
031        public static final int CLASS_OTHER_IDX         = 4;
032    
033        // Indexes to retrieve field and method information
034        public static final int MEMBER_ORIGIN_IDX = 0;
035        public static final int MEMBER_MODIFIER_IDX = 1;
036        public static final int MEMBER_DECLARER_IDX = 2;
037        public static final int MEMBER_TYPE_IDX = 3;
038        public static final int MEMBER_NAME_IDX = 4;
039        public static final int MEMBER_PARAMS_IDX = 5;
040        public static final int MEMBER_VALUE_IDX = 5;
041        public static final int MEMBER_EXCEPTIONS_IDX = 6;
042    
043        public static final String NOT_APPLICABLE = "n/a";
044        public static final String GROOVY = "GROOVY";
045        public static final String JAVA = "JAVA";
046    
047        /**
048         * @param objectUnderInspection must not be null
049         */
050        public Inspector(Object objectUnderInspection) {
051            if (null == objectUnderInspection){
052                throw new IllegalArgumentException("argument must not be null");
053            }
054            this.objectUnderInspection = objectUnderInspection;
055        }
056    
057        /**
058         * Get the Class Properties of the object under inspection.
059         * @return String array to be indexed by the CLASS_xxx_IDX constants
060         */
061        public String[] getClassProps() {
062            String[] result = new String[CLASS_OTHER_IDX+1];
063            Package pack = getClassUnderInspection().getPackage();
064            result[CLASS_PACKAGE_IDX] = "package "+ ((pack == null) ? NOT_APPLICABLE : pack.getName());
065            String modifiers = Modifier.toString(getClassUnderInspection().getModifiers());
066            String classOrInterface = "class";
067            if (getClassUnderInspection().isInterface()){
068                classOrInterface = "interface";
069            }
070            result[CLASS_CLASS_IDX] = modifiers + " "+ classOrInterface+" "+ shortName(getClassUnderInspection());
071            result[CLASS_INTERFACE_IDX] = "implements ";
072            Class[] interfaces = getClassUnderInspection().getInterfaces();
073            for (int i = 0; i < interfaces.length; i++) {
074                result[CLASS_INTERFACE_IDX] += shortName(interfaces[i])+ " ";
075            }
076            result[CLASS_SUPERCLASS_IDX] = "extends " + shortName(getClassUnderInspection().getSuperclass());
077            result[CLASS_OTHER_IDX] = "is Primitive: "+getClassUnderInspection().isPrimitive()
078                      +", is Array: "   +getClassUnderInspection().isArray()
079                      +", is Groovy: "  + isGroovy();
080            return result;
081        }
082    
083        public boolean isGroovy() {
084            return getClassUnderInspection().isAssignableFrom(GroovyObject.class);
085        }
086        
087        /**
088         * Gets the object being inspected.
089         */
090        public Object getObject() {
091            return objectUnderInspection;
092        }
093    
094        /**
095         * Get info about usual Java instance and class Methods as well as Constructors.
096         * @return  Array of StringArrays that can be indexed with the MEMBER_xxx_IDX constants
097         */
098        public Object[] getMethods(){
099            Method[] methods = getClassUnderInspection().getMethods();
100            Constructor[] ctors = getClassUnderInspection().getConstructors();
101            Object[] result = new Object[methods.length + ctors.length];
102            int resultIndex = 0;
103            for (; resultIndex < methods.length; resultIndex++) {
104                Method method = methods[resultIndex];
105                result[resultIndex] = methodInfo(method);
106            }
107            for (int i = 0; i < ctors.length; i++, resultIndex++) {
108                Constructor ctor = ctors[i];
109                result[resultIndex] = methodInfo(ctor);
110            }
111            return result;
112        }
113         /**
114         * Get info about instance and class Methods that are dynamically added through Groovy.
115         * @return  Array of StringArrays that can be indexed with the MEMBER_xxx_IDX constants
116         */
117        public Object[] getMetaMethods(){
118            MetaClass metaClass = InvokerHelper.getMetaClass(objectUnderInspection);
119            List metaMethods = metaClass.getMetaMethods();
120            Object[] result = new Object[metaMethods.size()];
121            int i=0;
122            for (Iterator iter = metaMethods.iterator(); iter.hasNext(); i++) {
123                MetaMethod metaMethod = (MetaMethod) iter.next();
124                result[i] = methodInfo(metaMethod);
125            }
126            return result;
127        }
128        
129        /**
130         * Get info about usual Java public fields incl. constants.
131         * @return  Array of StringArrays that can be indexed with the MEMBER_xxx_IDX constants
132         */
133        public Object[] getPublicFields(){
134            Field[] fields = getClassUnderInspection().getFields();
135            Object[] result = new Object[fields.length];
136            for (int i = 0; i < fields.length; i++) {
137                Field field = fields[i];
138                result[i] = fieldInfo(field);
139            }
140            return result;
141        }
142        /**
143         * Get info about Properties (Java and Groovy alike).
144         * @return  Array of StringArrays that can be indexed with the MEMBER_xxx_IDX constants
145         */
146        public Object[] getPropertyInfo(){
147            List props = DefaultGroovyMethods.getMetaPropertyValues(objectUnderInspection);
148            Object[] result = new Object[props.size()];
149            int i=0;
150            for (Iterator iter = props.iterator(); iter.hasNext(); i++) {
151                PropertyValue pv = (PropertyValue) iter.next();
152                result[i] = fieldInfo(pv);
153            }
154            return result;
155        }
156    
157        protected String[] fieldInfo(Field field) {
158            String[] result = new String[MEMBER_VALUE_IDX+1];
159            result[MEMBER_ORIGIN_IDX] = JAVA;
160            result[MEMBER_MODIFIER_IDX] = Modifier.toString(field.getModifiers());
161            result[MEMBER_DECLARER_IDX] = shortName(field.getDeclaringClass());
162            result[MEMBER_TYPE_IDX] = shortName(field.getType());
163            result[MEMBER_NAME_IDX] = field.getName();
164            try {
165                result[MEMBER_VALUE_IDX] = InvokerHelper.inspect(field.get(objectUnderInspection));
166            } catch (IllegalAccessException e) {
167                result[MEMBER_VALUE_IDX] = NOT_APPLICABLE;
168            }
169            return withoutNulls(result);
170        }
171        protected String[] fieldInfo(PropertyValue pv) {
172            String[] result = new String[MEMBER_VALUE_IDX+1];
173            result[MEMBER_ORIGIN_IDX] = GROOVY;
174            result[MEMBER_MODIFIER_IDX] = "public";
175            result[MEMBER_DECLARER_IDX] = NOT_APPLICABLE;
176            result[MEMBER_TYPE_IDX] = shortName(pv.getType());
177            result[MEMBER_NAME_IDX] = pv.getName();
178            try {
179                result[MEMBER_VALUE_IDX] = InvokerHelper.inspect(pv.getValue());
180            } catch (Exception e) {
181                result[MEMBER_VALUE_IDX] = NOT_APPLICABLE;
182            }
183            return withoutNulls(result);
184        }
185    
186        protected Class getClassUnderInspection() {
187            return objectUnderInspection.getClass();
188        }
189    
190        public static String shortName(Class clazz){
191            if (null == clazz) return NOT_APPLICABLE;
192            String className = clazz.getName();
193            if (null == clazz.getPackage()) return className;
194            String packageName = clazz.getPackage().getName();
195            int offset = packageName.length();
196            if (offset > 0) offset++;
197            className = className.substring(offset);
198            return className;
199        }
200    
201        protected String[] methodInfo(Method method){
202            String[] result = new String[MEMBER_EXCEPTIONS_IDX+1];
203                int mod = method.getModifiers();
204            result[MEMBER_ORIGIN_IDX] = JAVA;
205            result[MEMBER_MODIFIER_IDX] = Modifier.toString(mod);
206            result[MEMBER_DECLARER_IDX] = shortName(method.getDeclaringClass());
207            result[MEMBER_TYPE_IDX] = shortName(method.getReturnType());
208            result[MEMBER_NAME_IDX] = method.getName();
209                Class[] params = method.getParameterTypes();
210            StringBuffer sb = new StringBuffer();
211                for (int j = 0; j < params.length; j++) {
212                        sb.append(shortName(params[j]));
213                        if (j < (params.length - 1)) sb.append(", ");
214                }
215            result[MEMBER_PARAMS_IDX] = sb.toString();
216                sb.setLength(0);
217                Class[] exceptions = method.getExceptionTypes();
218                    for (int k = 0; k < exceptions.length; k++) {
219                        sb.append(shortName(exceptions[k]));
220                        if (k < (exceptions.length - 1)) sb.append(", ");
221                }
222            result[MEMBER_EXCEPTIONS_IDX] = sb.toString();
223                return withoutNulls(result);
224        }
225        protected String[] methodInfo(Constructor ctor){
226            String[] result = new String[MEMBER_EXCEPTIONS_IDX+1];
227                int mod = ctor.getModifiers();
228            result[MEMBER_ORIGIN_IDX] = JAVA;
229            result[MEMBER_MODIFIER_IDX] = Modifier.toString(mod);
230            result[MEMBER_DECLARER_IDX] = shortName(ctor.getDeclaringClass());
231            result[MEMBER_TYPE_IDX] = shortName(ctor.getDeclaringClass());
232            result[MEMBER_NAME_IDX] = ctor.getName();
233                Class[] params = ctor.getParameterTypes();
234            StringBuffer sb = new StringBuffer();
235                for (int j = 0; j < params.length; j++) {
236                        sb.append(shortName(params[j]));
237                        if (j < (params.length - 1)) sb.append(", ");
238                }
239            result[MEMBER_PARAMS_IDX] = sb.toString();
240                sb.setLength(0);
241                Class[] exceptions = ctor.getExceptionTypes();
242                    for (int k = 0; k < exceptions.length; k++) {
243                        sb.append(shortName(exceptions[k]));
244                        if (k < (exceptions.length - 1)) sb.append(", ");
245                }
246            result[MEMBER_EXCEPTIONS_IDX] = sb.toString();
247                return withoutNulls(result);
248        }
249        protected String[] methodInfo(MetaMethod method){
250            String[] result = new String[MEMBER_EXCEPTIONS_IDX+1];
251                int mod = method.getModifiers();
252            result[MEMBER_ORIGIN_IDX] = GROOVY;
253            result[MEMBER_MODIFIER_IDX] = Modifier.toString(mod);
254            result[MEMBER_DECLARER_IDX] = shortName(method.getDeclaringClass());
255            result[MEMBER_TYPE_IDX] = shortName(method.getReturnType());
256            result[MEMBER_NAME_IDX] = method.getName();
257                Class[] params = method.getParameterTypes();
258            StringBuffer sb = new StringBuffer();
259                for (int j = 0; j < params.length; j++) {
260                        sb.append(shortName(params[j]));
261                        if (j < (params.length - 1)) sb.append(", ");
262                }
263            result[MEMBER_PARAMS_IDX] = sb.toString();
264            result[MEMBER_EXCEPTIONS_IDX] = NOT_APPLICABLE; // no exception info for Groovy MetaMethods
265            return withoutNulls(result);
266        }
267    
268        protected String[] withoutNulls(String[] toNormalize){
269            for (int i = 0; i < toNormalize.length; i++) {
270                String s = toNormalize[i];
271                if (null == s) toNormalize[i] = NOT_APPLICABLE;
272            }
273            return toNormalize;
274        }
275    
276        public static void print(Object[] memberInfo) {
277            for (int i = 0; i < memberInfo.length; i++) {
278                String[] metaMethod = (String[]) memberInfo[i];
279                System.out.print(i+":\t");
280                for (int j = 0; j < metaMethod.length; j++) {
281                    String s = metaMethod[j];
282                    System.out.print(s+" ");
283                }
284                System.out.println("");
285            }
286        }
287        public static Collection sort(List memberInfo) {
288            Collections.sort(memberInfo, new MemberComparator());
289            return memberInfo;
290        }
291    
292        public static class MemberComparator implements Comparator {
293            public int compare(Object a, Object b) {
294                String[] aStr = (String[]) a;
295                String[] bStr = (String[]) b;
296                int result = aStr[Inspector.MEMBER_NAME_IDX].compareTo(bStr[Inspector.MEMBER_NAME_IDX]);
297                if (0 != result) return result;
298                result = aStr[Inspector.MEMBER_TYPE_IDX].compareTo(bStr[Inspector.MEMBER_TYPE_IDX]);
299                if (0 != result) return result;
300                result = aStr[Inspector.MEMBER_PARAMS_IDX].compareTo(bStr[Inspector.MEMBER_PARAMS_IDX]);
301                if (0 != result) return result;
302                result = aStr[Inspector.MEMBER_DECLARER_IDX].compareTo(bStr[Inspector.MEMBER_DECLARER_IDX]);
303                if (0 != result) return result;
304                result = aStr[Inspector.MEMBER_MODIFIER_IDX].compareTo(bStr[Inspector.MEMBER_MODIFIER_IDX]);
305                if (0 != result) return result;
306                result = aStr[Inspector.MEMBER_ORIGIN_IDX].compareTo(bStr[Inspector.MEMBER_ORIGIN_IDX]);
307                return result;
308            }
309        }
310    }