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            result[CLASS_PACKAGE_IDX] = "package "+ getClassUnderInspection().getPackage().getName();
064            String modifiers = Modifier.toString(getClassUnderInspection().getModifiers());
065            String classOrInterface = "class";
066            if (getClassUnderInspection().isInterface()){
067                classOrInterface = "interface";
068            }
069            result[CLASS_CLASS_IDX] = modifiers + " "+ classOrInterface+" "+ shortName(getClassUnderInspection());
070            result[CLASS_INTERFACE_IDX] = "implements ";
071            Class[] interfaces = getClassUnderInspection().getInterfaces();
072            for (int i = 0; i < interfaces.length; i++) {
073                result[CLASS_INTERFACE_IDX] += shortName(interfaces[i])+ " ";
074            }
075            result[CLASS_SUPERCLASS_IDX] = "extends " + shortName(getClassUnderInspection().getSuperclass());
076            result[CLASS_OTHER_IDX] = "is Primitive: "+getClassUnderInspection().isPrimitive()
077                      +", is Array: "   +getClassUnderInspection().isArray()
078                      +", is Groovy: "  + isGroovy();
079            return result;
080        }
081    
082        public boolean isGroovy() {
083            return getClassUnderInspection().isAssignableFrom(GroovyObject.class);
084        }
085    
086        /**
087         * Get info about usual Java instance and class Methods as well as Constructors.
088         * @return  Array of StringArrays that can be indexed with the MEMBER_xxx_IDX constants
089         */
090        public Object[] getMethods(){
091            Method[] methods = getClassUnderInspection().getMethods();
092            Constructor[] ctors = getClassUnderInspection().getConstructors();
093            Object[] result = new Object[methods.length + ctors.length];
094            int resultIndex = 0;
095            for (; resultIndex < methods.length; resultIndex++) {
096                Method method = methods[resultIndex];
097                result[resultIndex] = methodInfo(method);
098            }
099            for (int i = 0; i < ctors.length; i++, resultIndex++) {
100                Constructor ctor = ctors[i];
101                result[resultIndex] = methodInfo(ctor);
102            }
103            return result;
104        }
105         /**
106         * Get info about instance and class Methods that are dynamically added through Groovy.
107         * @return  Array of StringArrays that can be indexed with the MEMBER_xxx_IDX constants
108         */
109        public Object[] getMetaMethods(){
110            MetaClass metaClass = InvokerHelper.getMetaClass(objectUnderInspection);
111            List metaMethods = metaClass.getMetaMethods();
112            Object[] result = new Object[metaMethods.size()];
113            int i=0;
114            for (Iterator iter = metaMethods.iterator(); iter.hasNext(); i++) {
115                MetaMethod metaMethod = (MetaMethod) iter.next();
116                result[i] = methodInfo(metaMethod);
117            }
118            return result;
119        }
120        
121        /**
122         * Get info about usual Java public fields incl. constants.
123         * @return  Array of StringArrays that can be indexed with the MEMBER_xxx_IDX constants
124         */
125        public Object[] getPublicFields(){
126            Field[] fields = getClassUnderInspection().getFields();
127            Object[] result = new Object[fields.length];
128            for (int i = 0; i < fields.length; i++) {
129                Field field = fields[i];
130                result[i] = fieldInfo(field);
131            }
132            return result;
133        }
134        /**
135         * Get info about Properties (Java and Groovy alike).
136         * @return  Array of StringArrays that can be indexed with the MEMBER_xxx_IDX constants
137         */
138        public Object[] getProperties(){
139            List props = DefaultGroovyMethods.allProperties(objectUnderInspection);
140            Object[] result = new Object[props.size()];
141            int i=0;
142            for (Iterator iter = props.iterator(); iter.hasNext(); i++) {
143                PropertyValue pv = (PropertyValue) iter.next();
144                result[i] = fieldInfo(pv);
145            }
146            return result;
147        }
148    
149        protected String[] fieldInfo(Field field) {
150            String[] result = new String[MEMBER_VALUE_IDX+1];
151            result[MEMBER_ORIGIN_IDX] = JAVA;
152            result[MEMBER_MODIFIER_IDX] = Modifier.toString(field.getModifiers());
153            result[MEMBER_DECLARER_IDX] = shortName(field.getDeclaringClass());
154            result[MEMBER_TYPE_IDX] = shortName(field.getType());
155            result[MEMBER_NAME_IDX] = field.getName();
156            try {
157                result[MEMBER_VALUE_IDX] = field.get(objectUnderInspection).toString();
158            } catch (IllegalAccessException e) {
159                result[MEMBER_VALUE_IDX] = NOT_APPLICABLE;
160            }
161            return withoutNulls(result);
162        }
163        protected String[] fieldInfo(PropertyValue pv) {
164            String[] result = new String[MEMBER_VALUE_IDX+1];
165            result[MEMBER_ORIGIN_IDX] = GROOVY;
166            result[MEMBER_MODIFIER_IDX] = "public";
167            result[MEMBER_DECLARER_IDX] = NOT_APPLICABLE;
168            result[MEMBER_TYPE_IDX] = shortName(pv.getType());
169            result[MEMBER_NAME_IDX] = pv.getName();
170            try {
171                result[MEMBER_VALUE_IDX] = pv.getValue().toString();
172            } catch (Exception e) {
173                result[MEMBER_VALUE_IDX] = NOT_APPLICABLE;
174            }
175            return withoutNulls(result);
176        }
177    
178        protected Class getClassUnderInspection() {
179            return objectUnderInspection.getClass();
180        }
181    
182        public static String shortName(Class clazz){
183            if (null == clazz) return NOT_APPLICABLE;
184            String className = clazz.getName();
185            if (null == clazz.getPackage()) return className;
186            String packageName = clazz.getPackage().getName();
187            int offset = packageName.length();
188            if (offset > 0) offset++;
189            className = className.substring(offset);
190            return className;
191        }
192    
193        protected String[] methodInfo(Method method){
194            String[] result = new String[MEMBER_EXCEPTIONS_IDX+1];
195                int mod = method.getModifiers();
196            result[MEMBER_ORIGIN_IDX] = JAVA;
197            result[MEMBER_MODIFIER_IDX] = Modifier.toString(mod);
198            result[MEMBER_DECLARER_IDX] = shortName(method.getDeclaringClass());
199            result[MEMBER_TYPE_IDX] = shortName(method.getReturnType());
200            result[MEMBER_NAME_IDX] = method.getName();
201                Class[] params = method.getParameterTypes();
202            StringBuffer sb = new StringBuffer();
203                for (int j = 0; j < params.length; j++) {
204                        sb.append(shortName(params[j]));
205                        if (j < (params.length - 1)) sb.append(", ");
206                }
207            result[MEMBER_PARAMS_IDX] = sb.toString();
208                sb.setLength(0);
209                Class[] exceptions = method.getExceptionTypes();
210                    for (int k = 0; k < exceptions.length; k++) {
211                        sb.append(shortName(exceptions[k]));
212                        if (k < (exceptions.length - 1)) sb.append(", ");
213                }
214            result[MEMBER_EXCEPTIONS_IDX] = sb.toString();
215                return withoutNulls(result);
216        }
217        protected String[] methodInfo(Constructor ctor){
218            String[] result = new String[MEMBER_EXCEPTIONS_IDX+1];
219                int mod = ctor.getModifiers();
220            result[MEMBER_ORIGIN_IDX] = JAVA;
221            result[MEMBER_MODIFIER_IDX] = Modifier.toString(mod);
222            result[MEMBER_DECLARER_IDX] = shortName(ctor.getDeclaringClass());
223            result[MEMBER_TYPE_IDX] = shortName(ctor.getDeclaringClass());
224            result[MEMBER_NAME_IDX] = ctor.getName();
225                Class[] params = ctor.getParameterTypes();
226            StringBuffer sb = new StringBuffer();
227                for (int j = 0; j < params.length; j++) {
228                        sb.append(shortName(params[j]));
229                        if (j < (params.length - 1)) sb.append(", ");
230                }
231            result[MEMBER_PARAMS_IDX] = sb.toString();
232                sb.setLength(0);
233                Class[] exceptions = ctor.getExceptionTypes();
234                    for (int k = 0; k < exceptions.length; k++) {
235                        sb.append(shortName(exceptions[k]));
236                        if (k < (exceptions.length - 1)) sb.append(", ");
237                }
238            result[MEMBER_EXCEPTIONS_IDX] = sb.toString();
239                return withoutNulls(result);
240        }
241        protected String[] methodInfo(MetaMethod method){
242            String[] result = new String[MEMBER_EXCEPTIONS_IDX+1];
243                int mod = method.getModifiers();
244            result[MEMBER_ORIGIN_IDX] = GROOVY;
245            result[MEMBER_MODIFIER_IDX] = Modifier.toString(mod);
246            result[MEMBER_DECLARER_IDX] = shortName(method.getDeclaringClass());
247            result[MEMBER_TYPE_IDX] = shortName(method.getReturnType());
248            result[MEMBER_NAME_IDX] = method.getName();
249                Class[] params = method.getParameterTypes();
250            StringBuffer sb = new StringBuffer();
251                for (int j = 0; j < params.length; j++) {
252                        sb.append(shortName(params[j]));
253                        if (j < (params.length - 1)) sb.append(", ");
254                }
255            result[MEMBER_PARAMS_IDX] = sb.toString();
256            result[MEMBER_EXCEPTIONS_IDX] = NOT_APPLICABLE; // no exception info for Groovy MetaMethods
257            return withoutNulls(result);
258        }
259    
260        protected String[] withoutNulls(String[] toNormalize){
261            for (int i = 0; i < toNormalize.length; i++) {
262                String s = toNormalize[i];
263                if (null == s) toNormalize[i] = NOT_APPLICABLE;
264            }
265            return toNormalize;
266        }
267    
268        public static void print(Object[] memberInfo) {
269            for (int i = 0; i < memberInfo.length; i++) {
270                String[] metaMethod = (String[]) memberInfo[i];
271                System.out.print(i+":\t");
272                for (int j = 0; j < metaMethod.length; j++) {
273                    String s = metaMethod[j];
274                    System.out.print(s+" ");
275                }
276                System.out.println("");
277            }
278        }
279        public static Object[] sort(Object[] memberInfo) {
280            Arrays.sort(memberInfo, new MemberComparator());
281            return memberInfo;
282        }
283    
284        public static class MemberComparator implements Comparator {
285            public int compare(Object a, Object b) {
286                String[] aStr = (String[]) a;
287                String[] bStr = (String[]) b;
288                int result = aStr[Inspector.MEMBER_NAME_IDX].compareTo(bStr[Inspector.MEMBER_NAME_IDX]);
289                if (0 != result) return result;
290                result = aStr[Inspector.MEMBER_TYPE_IDX].compareTo(bStr[Inspector.MEMBER_TYPE_IDX]);
291                if (0 != result) return result;
292                result = aStr[Inspector.MEMBER_PARAMS_IDX].compareTo(bStr[Inspector.MEMBER_PARAMS_IDX]);
293                if (0 != result) return result;
294                result = aStr[Inspector.MEMBER_DECLARER_IDX].compareTo(bStr[Inspector.MEMBER_DECLARER_IDX]);
295                if (0 != result) return result;
296                result = aStr[Inspector.MEMBER_MODIFIER_IDX].compareTo(bStr[Inspector.MEMBER_MODIFIER_IDX]);
297                if (0 != result) return result;
298                result = aStr[Inspector.MEMBER_ORIGIN_IDX].compareTo(bStr[Inspector.MEMBER_ORIGIN_IDX]);
299                return result;
300            }
301        }
302    }