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
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
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;
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 }