View Javadoc

1   package groovy.inspect;
2   
3   import groovy.lang.GroovyObject;
4   import groovy.lang.MetaClass;
5   import groovy.lang.MetaMethod;
6   import groovy.lang.PropertyValue;
7   
8   import java.lang.reflect.Modifier;
9   import java.lang.reflect.Method;
10  import java.lang.reflect.Field;
11  import java.lang.reflect.Constructor;
12  import java.util.*;
13  
14  import org.codehaus.groovy.runtime.InvokerHelper;
15  import org.codehaus.groovy.runtime.DefaultGroovyMethods;
16  
17  /***
18   * The Inspector provides a unified access to an object's
19   * information that can be determined by introspection.
20   *
21   * @author Dierk Koenig
22   */
23  public class Inspector {
24      protected Object objectUnderInspection = null;
25  
26      // Indexes to retrieve Class Property information
27      public static final int CLASS_PACKAGE_IDX       = 0;
28      public static final int CLASS_CLASS_IDX         = 1;
29      public static final int CLASS_INTERFACE_IDX     = 2;
30      public static final int CLASS_SUPERCLASS_IDX    = 3;
31      public static final int CLASS_OTHER_IDX         = 4;
32  
33      // Indexes to retrieve field and method information
34      public static final int MEMBER_ORIGIN_IDX = 0;
35      public static final int MEMBER_MODIFIER_IDX = 1;
36      public static final int MEMBER_DECLARER_IDX = 2;
37      public static final int MEMBER_TYPE_IDX = 3;
38      public static final int MEMBER_NAME_IDX = 4;
39      public static final int MEMBER_PARAMS_IDX = 5;
40      public static final int MEMBER_VALUE_IDX = 5;
41      public static final int MEMBER_EXCEPTIONS_IDX = 6;
42  
43      public static final String NOT_APPLICABLE = "n/a";
44      public static final String GROOVY = "GROOVY";
45      public static final String JAVA = "JAVA";
46  
47      /***
48       * @param objectUnderInspection must not be null
49       */
50      public Inspector(Object objectUnderInspection) {
51          if (null == objectUnderInspection){
52              throw new IllegalArgumentException("argument must not be null");
53          }
54          this.objectUnderInspection = objectUnderInspection;
55      }
56  
57      /***
58       * Get the Class Properties of the object under inspection.
59       * @return String array to be indexed by the CLASS_xxx_IDX constants
60       */
61      public String[] getClassProps() {
62          String[] result = new String[CLASS_OTHER_IDX+1];
63          Package pack = getClassUnderInspection().getPackage();
64          result[CLASS_PACKAGE_IDX] = "package "+ ((pack == null) ? NOT_APPLICABLE : pack.getName());
65          String modifiers = Modifier.toString(getClassUnderInspection().getModifiers());
66          String classOrInterface = "class";
67          if (getClassUnderInspection().isInterface()){
68              classOrInterface = "interface";
69          }
70          result[CLASS_CLASS_IDX] = modifiers + " "+ classOrInterface+" "+ shortName(getClassUnderInspection());
71          result[CLASS_INTERFACE_IDX] = "implements ";
72          Class[] interfaces = getClassUnderInspection().getInterfaces();
73          for (int i = 0; i < interfaces.length; i++) {
74              result[CLASS_INTERFACE_IDX] += shortName(interfaces[i])+ " ";
75          }
76          result[CLASS_SUPERCLASS_IDX] = "extends " + shortName(getClassUnderInspection().getSuperclass());
77          result[CLASS_OTHER_IDX] = "is Primitive: "+getClassUnderInspection().isPrimitive()
78                    +", is Array: "   +getClassUnderInspection().isArray()
79                    +", is Groovy: "  + isGroovy();
80          return result;
81      }
82  
83      public boolean isGroovy() {
84          return getClassUnderInspection().isAssignableFrom(GroovyObject.class);
85      }
86      
87      /***
88       * Gets the object being inspected.
89       */
90      public Object getObject() {
91      	return objectUnderInspection;
92      }
93  
94      /***
95       * Get info about usual Java instance and class Methods as well as Constructors.
96       * @return  Array of StringArrays that can be indexed with the MEMBER_xxx_IDX constants
97       */
98      public Object[] getMethods(){
99          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</strong> 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 }