package com.ronsoft.books.nio.appendix; import java.lang.reflect.*; import java.util.*; import java.util.regex.*; import java.io.*; /** * Extract information about a class. This class was used to generate * the quick reference for the NIO book for O'Reilly. It's kind of ugly * and there are a lot of hacks in here. The most obvious one is the * wrapping of tags around class and member names in the * toString() output. * With a little work this could be made into a generic class introspector, * but it currently has some warts on it. * * Created May 2002 * @author Ron Hitchens (ron@ronsoft.com) * @version $Id: ClassInfo.java,v 1.2 2002/05/31 04:16:13 ron Exp $ */ public class ClassInfo { private static final String SRC_DIR = "/usr/local/java/java1.4/src"; private Package packg; private Class thisClass; private Class superClass; private Class declaringClass; private Class [] interfaces; private Field [] fields; private Constructor [] constructors; private Method [] methods; private Class [] classes; private ClassInfo [] internalClasses; private int mods = Modifier.PUBLIC; public ClassInfo (String className) throws Exception { this (Class.forName (className)); } public ClassInfo (String className, boolean prot) throws Exception { this (Class.forName (className), (prot) ? Modifier.PUBLIC | Modifier.PROTECTED : Modifier.PUBLIC); } public ClassInfo (Class clas) throws Exception { this (clas, Modifier.PUBLIC); } public ClassInfo (Class clas, int mods) throws Exception { thisClass = clas; packg = clas.getPackage(); superClass = clas.getSuperclass(); interfaces = clas.getInterfaces(); declaringClass = clas.getDeclaringClass(); fields = trimFields (clas.getDeclaredFields(), mods); constructors = trimConstructors (clas.getConstructors(), mods); methods = trimMethods (clas.getDeclaredMethods(), mods); classes = trimClasses (clas.getDeclaredClasses(), mods); if (classes.length == 0) { internalClasses = new ClassInfo [0]; } else { List list = new LinkedList(); for (int i = 0; i < classes.length; i++) { list.add (new ClassInfo (classes [i])); } internalClasses = new ClassInfo [list.size()]; list.toArray (internalClasses); } } // --------------------------------------------------------------- public boolean isInterface() { return (thisClass.isInterface()); } public Class getSuperClass() { if ((superClass == null) || (superClass == Object.class)) { return (null); } return (superClass); } // --------------------------------------------------------------- // Given an array of Class or Member, return a new array containing // only those elements with the matching modifier bits set. // This is to filter out private and protected methods for example. private Field [] trimFields (Field [] item, int mods) { List list = new LinkedList(); for (int i = 0; i < item.length; i++) { if ((item [i].getModifiers() & mods) != 0) { list.add (item [i]); } } Field [] trimmed = new Field [list.size()]; list.toArray (trimmed); Arrays.sort (trimmed, comper); return (trimmed); } private Constructor [] trimConstructors (Constructor [] item, int mods) { List list = new LinkedList(); for (int i = 0; i < item.length; i++) { if ((item [i].getModifiers() & mods) != 0) { list.add (item [i]); } } Constructor [] trimmed = new Constructor [list.size()]; list.toArray (trimmed); Arrays.sort (trimmed, comper); return (trimmed); } private Method [] trimMethods (Method [] item, int mods) { List list = new LinkedList(); for (int i = 0; i < item.length; i++) { if ((item [i].getModifiers() & mods) != 0) { list.add (item [i]); } } Method [] trimmed = new Method [list.size()]; list.toArray (trimmed); Arrays.sort (trimmed, comper); return (trimmed); } private Class [] trimClasses (Class [] item, int mods) { List list = new LinkedList(); for (int i = 0; i < item.length; i++) { if ((item [i].getModifiers() & mods) != 0) { list.add (item [i]); } } Class [] trimmed = new Class [list.size()]; list.toArray (trimmed); Arrays.sort (trimmed, comper); return (trimmed); } private Comper comper = new Comper(); private class Comper implements Comparator { public int compare (Object o1, Object o2) { if (o1 instanceof Class) { Class c1 = (Class) o1; Class c2 = (Class) o2; String n1 = c1.getName(); String n2 = c2.getName(); if (c1.isArray()) { n1 = c1.getComponentType().getName(); } if (c2.isArray()) { n2 = c2.getComponentType().getName(); } return (n1.compareTo (n2)); } Member m1 = (Member) o1; Member m2 = (Member) o2; String n1 = m1.getName(); String n2 = m2.getName(); if (( ! n1.equals (n2)) || (m1 instanceof Field)) { return (n1.compareTo (n2)); } if (m1 instanceof Constructor) { return (compareArgs ( ((Constructor) m1).getParameterTypes(), ((Constructor) m2).getParameterTypes())); } return (compareArgs (((Method) m1).getParameterTypes(), ((Method) m2).getParameterTypes())); } private int compareArgs (Class [] p1, Class [] p2) { int n = p1.length - p2.length; if (n != 0) { return (n); } for (int i = 0; i < p1.length; i++) { n = comper.compare (p1 [i], p2 [i]); if (n != 0) { return (n); } } return (0); } public boolean equals (Object o1) { return (false); } } // --------------------------------------------------------------- public String getSimpleClassName (String name) { String rep = name.replace ('$', '.'); int n = rep.lastIndexOf ("."); if (n == -1) { return (rep); } return (rep.substring (n + 1)); } public String getSimpleClassName (Class clas) { return (getSimpleClassName (clas.getName())); } public String getClassName (String name) { String pkgname = packg.getName(); if (name.startsWith ("java.lang")) { return (name.substring ("java.lang".length() + 1)); } if (name.startsWith (pkgname)) { String simple = name.substring (pkgname.length() + 1); // is the package name the whole prefix? if (simple.indexOf (".") == -1) { return (simple.replace ('$', '.')); } } return (name.replace ('$', '.')); } public String getClassName (Class clas) { return (getClassName (clas.getName())); } public String getClassName() { return (getClassName (thisClass)); } public String getModifierNames (int mods) { if (thisClass.isInterface()) { mods &= ~Modifier.ABSTRACT; } return (Modifier.toString (mods)); } public String getModifierNames() { return (getModifierNames (thisClass.getModifiers())); } private void indent (StringBuffer sb, String indent, int level) { for (int i = 0; i < level; i++) { sb.append (indent); } } // --------------------------------------------------------------- public String toString() { return (toString ("\t")); } public String toString (String indent) { return (toString (indent, true)); } public String toString (String indent, boolean listPackage) { StringBuffer sb = new StringBuffer(); stringify (sb, indent, 0, listPackage); return (sb.toString()); } void stringify (StringBuffer sb, String indent, int indentLevel, boolean listPackage) { if (listPackage) { indent (sb, indent, indentLevel); sb.append ("package ").append (packg.getName()); sb.append ("\n\n"); } indent (sb, indent, indentLevel); sb.append (getModifierNames()); if (thisClass.isInterface()) { sb.append (" "); } else { sb.append (" class "); } // HACK! This should be better parameterized sb.append (""); sb.append (getClassName()); sb.append (""); Class sc = getSuperClass(); if (sc != null) { sb.append ("\n"); indent (sb, indent, indentLevel); sb.append (indent).append ("extends "); sb.append (getClassName (sc)); } if (interfaces.length > 0) { sb.append ("\n"); indent (sb, indent, indentLevel); sb.append (indent); if (isInterface()) { sb.append ("extends "); } else { sb.append ("implements "); } for (int i = 0; i < interfaces.length; i++) { if (i != 0) { sb.append (", "); } sb.append (getClassName (interfaces [i])); } } sb.append ("\n"); indent (sb, indent, indentLevel); sb.append ("{"); sb.append ("\n"); boolean needsep = false; if (fields.length > 0) { printMembers (sb, fields, indent, indentLevel + 1); needsep = true; } if (constructors.length > 0) { if (needsep) sb.append ("\n"); printMembers (sb, constructors, indent, indentLevel + 1); needsep = true; } if (methods.length > 0) { if (needsep) sb.append ("\n"); printMembers (sb, methods, indent, indentLevel + 1); needsep = true; } if (internalClasses.length > 0) { if (needsep) sb.append ("\n"); for (int i = 0; i < internalClasses.length; i++) { if ( ! Modifier.isPublic (classes [i].getModifiers())) { continue; } if (i != 0) sb.append ("\n"); internalClasses [i].stringify (sb, indent, indentLevel + 1, false); // nl is suppressed at end of classes sb.append ("\n"); } } indent (sb, indent, indentLevel); sb.append ("}"); } private void printType (StringBuffer sb, Class type) { if (type.isArray()) { sb.append (getClassName (type.getComponentType())); sb.append (" []"); } else { sb.append (getClassName (type)); } } private void printMembers (StringBuffer sb, Member [] members, String indent, int indentLevel) { for (int i = 0; i < members.length; i++) { Member member = members [i]; int mods = member.getModifiers(); if (Modifier.isPrivate (mods)) { continue; } if (( ! Modifier.isPublic (mods)) && (! Modifier.isProtected (mods))) { continue; // package private } indent (sb, indent, indentLevel); sb.append (getModifierNames (mods)); if (member instanceof Field) { Field field = (Field) member; sb.append (" "); printType (sb, field.getType()); } if (member instanceof Method) { Method method = (Method) member; sb.append (" "); printType (sb, method.getReturnType()); } sb.append (" "); sb.append (""); sb.append (getClassName (member.getName())); sb.append (""); if ((member instanceof Constructor) || (member instanceof Method)) { printArgs (sb, member); printExcept (sb, member, indent, indentLevel); } if (Modifier.isAbstract (mods)) { sb.append (";"); } sb.append ("\n"); } } private void printArgs (StringBuffer sb, Member member) { Class [] paramTypes = null; if (member instanceof Constructor) { Constructor c = (Constructor) member; paramTypes = c.getParameterTypes(); } if (member instanceof Method) { Method m = (Method) member; paramTypes = m.getParameterTypes(); } if ((paramTypes == null) || (paramTypes.length == 0)) { sb.append ("()"); return; } sb.append (" ("); String [] names = getArgNames (member, paramTypes); for (int i = 0; i < paramTypes.length; i++) { Class p = paramTypes [i]; if (i != 0) { sb.append (", "); } printType (sb, p); if (names == null) { sb.append (" XXX"); } else { sb.append (" ").append (names [i]); } } sb.append (")"); } private void printExcept (StringBuffer sb, Member member, String indent, int indentLevel) { Class [] exs = null; if (member instanceof Constructor) { exs = ((Constructor) member).getExceptionTypes(); } if (member instanceof Method) { exs = ((Method) member).getExceptionTypes(); } if (exs.length == 0) { return; } sb.append ("\n"); indent (sb, indent, indentLevel + 1); sb.append ("throws "); for (int i = 0; i < exs.length; i++) { if (i != 0) { sb.append (", "); } sb.append (getClassName (exs [i])); } } // --------------------------------------------------------------- private String [] zeroStringArray = new String [0]; private String [] getArgNames (Member member, Class [] params) { if (params.length == 0) { return (zeroStringArray); } CharSequence cs = null; try { // This is inefficient, the file is loaded for // each constructor/method. The source file should // be loaded in the constructor and re-used. cs = loadFile (srcFileName (thisClass.getName())); } catch (Exception e) { System.out.println ("Can't open file: " + e); return (null); } boolean isMethod = (member instanceof Method); StringBuffer sb = new StringBuffer(); String s; sb.append ("(?ms)^\\s*"); s = getModifierNames (member.getModifiers()); sb.append (s.replaceAll ("\\s+", "\\\\s+")); sb.append ("\\s+"); if (isMethod) { Method method = (Method) member; appendRegexType (sb, method.getReturnType()); sb.append ("\\s+"); } sb.append (getSimpleClassName (member.getName())); sb.append ("\\s*\\("); for (int i = 0; i < params.length; i++) { if (i != 0) { sb.append (",\\s*"); } sb.append ("\\s*\\w*?\\s*"); Class p = params [i]; appendRegexType (sb, p); sb.append ("\\s+"); sb.append ("(\\w+)\\s*"); } sb.append ("\\)"); String regex = sb.toString(); Pattern pat = Pattern.compile (regex); Matcher matcher = pat.matcher (cs); if ( ! matcher.find()) { System.out.println (getSimpleClassName (thisClass) + ": no match='" + regex + "'"); return (null); } int count = matcher.groupCount(); String [] names = new String [count]; for (int i = 0; i < count; i++) { names [i] = matcher.group (i + 1); } return (names); } private void appendRegexType (StringBuffer sb, Class t) { if (t.isArray()) { sb.append (getSimpleClassName (t.getComponentType())); sb.append ("\\s*\\[\\]\\s*"); } else { sb.append (getSimpleClassName (t)); } } private String srcFileName (String className) { return (SRC_DIR + "/" + className.replaceAll ("\\.", "/") + ".java"); } private CharSequence loadFile (String name) throws Exception { BufferedReader in = new BufferedReader (new FileReader (name)); StringBuffer sb = new StringBuffer(); String s; while ((s = in.readLine()) != null) { sb.append (s); sb.append ("\n"); } in.close(); return sb; } // --------------------------------------------------------------- public static void main (String[] argv) throws Exception { for (int i = 0; i < argv.length; i++) { ClassInfo ci = new ClassInfo (argv [i]); System.out.println (new ClassInfo (argv [i]).toString()); } } }