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