/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package groovy.inspect; import groovy.lang.GroovyObject; import groovy.lang.MetaClass; import groovy.lang.MetaMethod; import groovy.lang.PropertyValue; import org.codehaus.groovy.reflection.CachedClass; import org.codehaus.groovy.runtime.DefaultGroovyMethods; import org.codehaus.groovy.runtime.InvokerHelper; import java.io.PrintStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; /** * The Inspector provides a unified access to an object's * information that can be determined by introspection. * * @author Dierk Koenig */ public class Inspector { protected Object objectUnderInspection; // Indexes to retrieve Class Property information public static final int CLASS_PACKAGE_IDX = 0; public static final int CLASS_CLASS_IDX = 1; public static final int CLASS_INTERFACE_IDX = 2; public static final int CLASS_SUPERCLASS_IDX = 3; public static final int CLASS_OTHER_IDX = 4; // Indexes to retrieve field and method information public static final int MEMBER_ORIGIN_IDX = 0; public static final int MEMBER_MODIFIER_IDX = 1; public static final int MEMBER_DECLARER_IDX = 2; public static final int MEMBER_TYPE_IDX = 3; public static final int MEMBER_NAME_IDX = 4; public static final int MEMBER_PARAMS_IDX = 5; public static final int MEMBER_VALUE_IDX = 5; public static final int MEMBER_EXCEPTIONS_IDX = 6; public static final String NOT_APPLICABLE = "n/a"; public static final String GROOVY = "GROOVY"; public static final String JAVA = "JAVA"; /** * @param objectUnderInspection must not be null */ public Inspector(Object objectUnderInspection) { if (null == objectUnderInspection) { throw new IllegalArgumentException("argument must not be null"); } this.objectUnderInspection = objectUnderInspection; } /** * Get the Class Properties of the object under inspection. * * @return String array to be indexed by the CLASS_xxx_IDX constants */ public String[] getClassProps() { String[] result = new String[CLASS_OTHER_IDX + 1]; Package pack = getClassUnderInspection().getPackage(); result[CLASS_PACKAGE_IDX] = "package " + ((pack == null) ? NOT_APPLICABLE : pack.getName()); String modifiers = Modifier.toString(getClassUnderInspection().getModifiers()); result[CLASS_CLASS_IDX] = modifiers + " class " + shortName(getClassUnderInspection()); result[CLASS_INTERFACE_IDX] = "implements "; Class[] interfaces = getClassUnderInspection().getInterfaces(); for (Class anInterface : interfaces) { result[CLASS_INTERFACE_IDX] += shortName(anInterface) + " "; } result[CLASS_SUPERCLASS_IDX] = "extends " + shortName(getClassUnderInspection().getSuperclass()); result[CLASS_OTHER_IDX] = "is Primitive: " + getClassUnderInspection().isPrimitive() + ", is Array: " + getClassUnderInspection().isArray() + ", is Groovy: " + isGroovy(); return result; } public boolean isGroovy() { return GroovyObject.class.isAssignableFrom(getClassUnderInspection()); } /** * Gets the object being inspected. * * @return the object */ public Object getObject() { return objectUnderInspection; } /** * Get info about usual Java instance and class Methods as well as Constructors. * * @return Array of StringArrays that can be indexed with the MEMBER_xxx_IDX constants */ public Object[] getMethods() { Method[] methods = getClassUnderInspection().getMethods(); Constructor[] ctors = getClassUnderInspection().getConstructors(); Object[] result = new Object[methods.length + ctors.length]; int resultIndex = 0; for (; resultIndex < methods.length; resultIndex++) { Method method = methods[resultIndex]; result[resultIndex] = methodInfo(method); } for (int i = 0; i < ctors.length; i++, resultIndex++) { Constructor ctor = ctors[i]; result[resultIndex] = methodInfo(ctor); } return result; } /** * Get info about instance and class Methods that are dynamically added through Groovy. * * @return Array of StringArrays that can be indexed with the MEMBER_xxx_IDX constants */ public Object[] getMetaMethods() { MetaClass metaClass = InvokerHelper.getMetaClass(objectUnderInspection); List metaMethods = metaClass.getMetaMethods(); Object[] result = new Object[metaMethods.size()]; int i = 0; for (Iterator iter = metaMethods.iterator(); iter.hasNext(); i++) { MetaMethod metaMethod = (MetaMethod) iter.next(); result[i] = methodInfo(metaMethod); } return result; } /** * Get info about usual Java public fields incl. constants. * * @return Array of StringArrays that can be indexed with the MEMBER_xxx_IDX constants */ public Object[] getPublicFields() { Field[] fields = getClassUnderInspection().getFields(); Object[] result = new Object[fields.length]; for (int i = 0; i < fields.length; i++) { Field field = fields[i]; result[i] = fieldInfo(field); } return result; } /** * Get info about Properties (Java and Groovy alike). * * @return Array of StringArrays that can be indexed with the MEMBER_xxx_IDX constants */ public Object[] getPropertyInfo() { List props = DefaultGroovyMethods.getMetaPropertyValues(objectUnderInspection); Object[] result = new Object[props.size()]; int i = 0; for (Iterator iter = props.iterator(); iter.hasNext(); i++) { PropertyValue pv = (PropertyValue) iter.next(); result[i] = fieldInfo(pv); } return result; } protected String[] fieldInfo(Field field) { String[] result = new String[MEMBER_VALUE_IDX + 1]; result[MEMBER_ORIGIN_IDX] = JAVA; result[MEMBER_MODIFIER_IDX] = Modifier.toString(field.getModifiers()); result[MEMBER_DECLARER_IDX] = shortName(field.getDeclaringClass()); result[MEMBER_TYPE_IDX] = shortName(field.getType()); result[MEMBER_NAME_IDX] = field.getName(); try { result[MEMBER_VALUE_IDX] = InvokerHelper.inspect(field.get(objectUnderInspection)); } catch (IllegalAccessException e) { result[MEMBER_VALUE_IDX] = NOT_APPLICABLE; } return withoutNulls(result); } protected String[] fieldInfo(PropertyValue pv) { String[] result = new String[MEMBER_VALUE_IDX + 1]; result[MEMBER_ORIGIN_IDX] = GROOVY; result[MEMBER_MODIFIER_IDX] = "public"; result[MEMBER_DECLARER_IDX] = NOT_APPLICABLE; result[MEMBER_TYPE_IDX] = shortName(pv.getType()); result[MEMBER_NAME_IDX] = pv.getName(); try { result[MEMBER_VALUE_IDX] = InvokerHelper.inspect(pv.getValue()); } catch (Exception e) { result[MEMBER_VALUE_IDX] = NOT_APPLICABLE; } return withoutNulls(result); } protected Class getClassUnderInspection() { return objectUnderInspection.getClass(); } public static String shortName(Class clazz) { if (null == clazz) return NOT_APPLICABLE; String className = clazz.getName(); if (null == clazz.getPackage()) return className; String packageName = clazz.getPackage().getName(); int offset = packageName.length(); if (offset > 0) offset++; className = className.substring(offset); return className; } protected String[] methodInfo(Method method) { String[] result = new String[MEMBER_EXCEPTIONS_IDX + 1]; int mod = method.getModifiers(); result[MEMBER_ORIGIN_IDX] = JAVA; result[MEMBER_DECLARER_IDX] = shortName(method.getDeclaringClass()); result[MEMBER_MODIFIER_IDX] = Modifier.toString(mod); result[MEMBER_NAME_IDX] = method.getName(); result[MEMBER_TYPE_IDX] = shortName(method.getReturnType()); Class[] params = method.getParameterTypes(); StringBuilder sb = new StringBuilder(); for (int j = 0; j < params.length; j++) { sb.append(shortName(params[j])); if (j < (params.length - 1)) sb.append(", "); } result[MEMBER_PARAMS_IDX] = sb.toString(); sb.setLength(0); Class[] exceptions = method.getExceptionTypes(); for (int k = 0; k < exceptions.length; k++) { sb.append(shortName(exceptions[k])); if (k < (exceptions.length - 1)) sb.append(", "); } result[MEMBER_EXCEPTIONS_IDX] = sb.toString(); return withoutNulls(result); } protected String[] methodInfo(Constructor ctor) { String[] result = new String[MEMBER_EXCEPTIONS_IDX + 1]; int mod = ctor.getModifiers(); result[MEMBER_ORIGIN_IDX] = JAVA; result[MEMBER_MODIFIER_IDX] = Modifier.toString(mod); result[MEMBER_DECLARER_IDX] = shortName(ctor.getDeclaringClass()); result[MEMBER_TYPE_IDX] = shortName(ctor.getDeclaringClass()); result[MEMBER_NAME_IDX] = ctor.getName(); Class[] params = ctor.getParameterTypes(); StringBuilder sb = new StringBuilder(); for (int j = 0; j < params.length; j++) { sb.append(shortName(params[j])); if (j < (params.length - 1)) sb.append(", "); } result[MEMBER_PARAMS_IDX] = sb.toString(); sb.setLength(0); Class[] exceptions = ctor.getExceptionTypes(); for (int k = 0; k < exceptions.length; k++) { sb.append(shortName(exceptions[k])); if (k < (exceptions.length - 1)) sb.append(", "); } result[MEMBER_EXCEPTIONS_IDX] = sb.toString(); return withoutNulls(result); } protected String[] methodInfo(MetaMethod method) { String[] result = new String[MEMBER_EXCEPTIONS_IDX + 1]; int mod = method.getModifiers(); result[MEMBER_ORIGIN_IDX] = GROOVY; result[MEMBER_MODIFIER_IDX] = Modifier.toString(mod); result[MEMBER_DECLARER_IDX] = shortName(method.getDeclaringClass().getTheClass()); result[MEMBER_TYPE_IDX] = shortName(method.getReturnType()); result[MEMBER_NAME_IDX] = method.getName(); CachedClass[] params = method.getParameterTypes(); StringBuilder sb = new StringBuilder(); for (int j = 0; j < params.length; j++) { sb.append(shortName(params[j].getTheClass())); if (j < (params.length - 1)) sb.append(", "); } result[MEMBER_PARAMS_IDX] = sb.toString(); result[MEMBER_EXCEPTIONS_IDX] = NOT_APPLICABLE; // no exception info for Groovy MetaMethods return withoutNulls(result); } protected String[] withoutNulls(String[] toNormalize) { for (int i = 0; i < toNormalize.length; i++) { String s = toNormalize[i]; if (null == s) toNormalize[i] = NOT_APPLICABLE; } return toNormalize; } public static void print(Object[] memberInfo) { print(System.out, memberInfo); } static void print(final PrintStream out, Object[] memberInfo) { for (int i = 0; i < memberInfo.length; i++) { String[] metaMethod = (String[]) memberInfo[i]; out.print(i + ":\t"); for (String s : metaMethod) { out.print(s + " "); } out.println(""); } } public static Collection sort(List<Object> memberInfo) { Collections.sort(memberInfo, new MemberComparator()); return memberInfo; } public static class MemberComparator implements Comparator<Object> { public int compare(Object a, Object b) { String[] aStr = (String[]) a; String[] bStr = (String[]) b; int result = aStr[Inspector.MEMBER_NAME_IDX].compareTo(bStr[Inspector.MEMBER_NAME_IDX]); if (0 != result) return result; result = aStr[Inspector.MEMBER_TYPE_IDX].compareTo(bStr[Inspector.MEMBER_TYPE_IDX]); if (0 != result) return result; result = aStr[Inspector.MEMBER_PARAMS_IDX].compareTo(bStr[Inspector.MEMBER_PARAMS_IDX]); if (0 != result) return result; result = aStr[Inspector.MEMBER_DECLARER_IDX].compareTo(bStr[Inspector.MEMBER_DECLARER_IDX]); if (0 != result) return result; result = aStr[Inspector.MEMBER_MODIFIER_IDX].compareTo(bStr[Inspector.MEMBER_MODIFIER_IDX]); if (0 != result) return result; result = aStr[Inspector.MEMBER_ORIGIN_IDX].compareTo(bStr[Inspector.MEMBER_ORIGIN_IDX]); return result; } } }