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