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