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          result[CLASS_PACKAGE_IDX] = "package "+ getClassUnderInspection().getPackage().getName();
64          String modifiers = Modifier.toString(getClassUnderInspection().getModifiers());
65          String classOrInterface = "class";
66          if (getClassUnderInspection().isInterface()){
67              classOrInterface = "interface";
68          }
69          result[CLASS_CLASS_IDX] = modifiers + " "+ classOrInterface+" "+ shortName(getClassUnderInspection());
70          result[CLASS_INTERFACE_IDX] = "implements ";
71          Class[] interfaces = getClassUnderInspection().getInterfaces();
72          for (int i = 0; i < interfaces.length; i++) {
73              result[CLASS_INTERFACE_IDX] += shortName(interfaces[i])+ " ";
74          }
75          result[CLASS_SUPERCLASS_IDX] = "extends " + shortName(getClassUnderInspection().getSuperclass());
76          result[CLASS_OTHER_IDX] = "is Primitive: "+getClassUnderInspection().isPrimitive()
77                    +", is Array: "   +getClassUnderInspection().isArray()
78                    +", is Groovy: "  + isGroovy();
79          return result;
80      }
81  
82      public boolean isGroovy() {
83          return getClassUnderInspection().isAssignableFrom(GroovyObject.class);
84      }
85  
86      /***
87       * Get info about usual Java instance and class Methods as well as Constructors.
88       * @return  Array of StringArrays that can be indexed with the MEMBER_xxx_IDX constants
89       */
90      public Object[] getMethods(){
91          Method[] methods = getClassUnderInspection().getMethods();
92          Constructor[] ctors = getClassUnderInspection().getConstructors();
93          Object[] result = new Object[methods.length + ctors.length];
94          int resultIndex = 0;
95          for (; resultIndex < methods.length; resultIndex++) {
96              Method method = methods[resultIndex];
97              result[resultIndex] = methodInfo(method);
98          }
99          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</strong> 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 }