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 Package pack = getClassUnderInspection().getPackage(); 064 result[CLASS_PACKAGE_IDX] = "package "+ ((pack == null) ? NOT_APPLICABLE : pack.getName()); 065 String modifiers = Modifier.toString(getClassUnderInspection().getModifiers()); 066 String classOrInterface = "class"; 067 if (getClassUnderInspection().isInterface()){ 068 classOrInterface = "interface"; 069 } 070 result[CLASS_CLASS_IDX] = modifiers + " "+ classOrInterface+" "+ shortName(getClassUnderInspection()); 071 result[CLASS_INTERFACE_IDX] = "implements "; 072 Class[] interfaces = getClassUnderInspection().getInterfaces(); 073 for (int i = 0; i < interfaces.length; i++) { 074 result[CLASS_INTERFACE_IDX] += shortName(interfaces[i])+ " "; 075 } 076 result[CLASS_SUPERCLASS_IDX] = "extends " + shortName(getClassUnderInspection().getSuperclass()); 077 result[CLASS_OTHER_IDX] = "is Primitive: "+getClassUnderInspection().isPrimitive() 078 +", is Array: " +getClassUnderInspection().isArray() 079 +", is Groovy: " + isGroovy(); 080 return result; 081 } 082 083 public boolean isGroovy() { 084 return getClassUnderInspection().isAssignableFrom(GroovyObject.class); 085 } 086 087 /** 088 * Gets the object being inspected. 089 */ 090 public Object getObject() { 091 return objectUnderInspection; 092 } 093 094 /** 095 * Get info about usual Java instance and class Methods as well as Constructors. 096 * @return Array of StringArrays that can be indexed with the MEMBER_xxx_IDX constants 097 */ 098 public Object[] getMethods(){ 099 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 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; // no exception info for Groovy MetaMethods 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 }