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 /** 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 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; // no exception info for Groovy MetaMethods 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 }