/** * Reflection.java * * A utility class for javacomplete mainly for reading class or package information. * Version: 0.77 * Maintainer: cheng fang * Last Change: 2007-09-16 * Copyright: Copyright (C) 2007 cheng fang. All rights reserved. * License: Vim License (see vim's :help license) * */ import java.lang.reflect.*; import java.io.*; import java.util.*; import java.util.zip.*; class Reflection { static final String VERSION = "0.77"; static final int OPTION_FIELD = 1; static final int OPTION_METHOD = 2; static final int OPTION_STATIC_FIELD = 4; static final int OPTION_STATIC_METHOD = 8; static final int OPTION_CONSTRUCTOR = 16; static final int OPTION_STATIC = 12; // compound static static final int OPTION_INSTANCE = 15; // compound instance static final int OPTION_ALL = 31; // compound all static final int OPTION_SUPER = 32; static final int OPTION_SAME_PACKAGE = 64; static final int STRATEGY_ALPHABETIC = 128; static final int STRATEGY_HIERARCHY = 256; static final int STRATEGY_DEFAULT = 512; static final int RETURN_ALL_PACKAGE_INFO = 0x1000; static final String KEY_NAME = "'n':"; // "'name':"; static final String KEY_TYPE = "'t':"; // "'type':"; static final String KEY_MODIFIER = "'m':"; // "'modifier':"; static final String KEY_PARAMETERTYPES = "'p':"; // "'parameterTypes':"; static final String KEY_RETURNTYPE = "'r':"; // "'returnType':"; static final String KEY_DESCRIPTION = "'d':"; // "'description':"; static final String KEY_DECLARING_CLASS = "'c':"; // "'declaringclass':"; static final String NEWLINE = ""; // "\r\n" static boolean debug_mode = false; static Hashtable htClasspath = new Hashtable(); public static boolean existed(String fqn) { boolean result = false; try { Class.forName(fqn); result = true; } catch (Exception ex) { } return result; } public static String existedAndRead(String fqns) { Hashtable mapPackages = new Hashtable(); // qualified name --> StringBuffer Hashtable mapClasses = new Hashtable(); // qualified name --> StringBuffer for (StringTokenizer st = new StringTokenizer(fqns, ","); st.hasMoreTokens(); ) { String fqn = st.nextToken(); try { Class clazz = Class.forName(fqn); putClassInfo(mapClasses, clazz); } catch (Exception ex) { String binaryName = fqn; boolean found = false; while (true) { try { int lastDotPos = binaryName.lastIndexOf('.'); if (lastDotPos == -1) break; binaryName = binaryName.substring(0, lastDotPos) + '$' + binaryName.substring(lastDotPos+1, binaryName.length()); Class clazz = Class.forName(binaryName); putClassInfo(mapClasses, clazz); found = true; break; } catch (Exception e) { } } if (!found) putPackageInfo(mapPackages, fqn); } } if (mapPackages.size() > 0 || mapClasses.size() > 0) { StringBuffer sb = new StringBuffer(4096); sb.append("{"); for (Enumeration e = mapPackages.keys(); e.hasMoreElements(); ) { String s = (String)e.nextElement(); sb.append("'").append( s.replace('$', '.') ).append("':").append(mapPackages.get(s)).append(","); } for (Enumeration e = mapClasses.keys(); e.hasMoreElements(); ) { String s = (String)e.nextElement(); sb.append("'").append( s.replace('$', '.') ).append("':").append(mapClasses.get(s)).append(","); } sb.append("}"); return sb.toString(); } else return ""; } private static String getPackageList(String fqn) { Hashtable mapPackages = new Hashtable(); putPackageInfo(mapPackages, fqn); return mapPackages.size() > 0 ? mapPackages.get(fqn).toString() : ""; } private static Hashtable collectClassPath() { if (!htClasspath.isEmpty()) return htClasspath; // runtime classes if ("Kaffe".equals(System.getProperty("java.vm.name"))) { addClasspathesFromDir(System.getProperty("java.home") + File.separator + "share" + File.separator + "kaffe" + File.separator); } else if ("GNU libgcj".equals(System.getProperty("java.vm.name"))) { if (new File(System.getProperty("sun.boot.class.path")).exists()) htClasspath.put(System.getProperty("sun.boot.class.path"), ""); } if (System.getProperty("java.vendor").toLowerCase(Locale.US).indexOf("microsoft") >= 0) { // `*.ZIP` files in `Packages` directory addClasspathesFromDir(System.getProperty("java.home") + File.separator + "Packages" + File.separator); } else { // the following code works for several kinds of JDK // - JDK1.1: classes.zip // - JDK1.2+: rt.jar // - JDK1.4+ of Sun and Apple: rt.jar + jce.jar + jsse.jar // - JDK1.4 of IBM split rt.jar into core.jar, graphics.jar, server.jar // combined jce.jar and jsse.jar into security.jar // - JDK for MacOS X split rt.jar into classes.jar, ui.jar in Classes directory addClasspathesFromDir(System.getProperty("java.home") + File.separator + "lib" + File.separator); addClasspathesFromDir(System.getProperty("java.home") + File.separator + "jre" + File.separator + "lib" + File.separator); addClasspathesFromDir(System.getProperty("java.home") + File.separator + ".." + File.separator + "Classes" + File.separator); } // ext String extdirs = System.getProperty("java.ext.dirs"); for (StringTokenizer st = new StringTokenizer(extdirs, File.pathSeparator); st.hasMoreTokens(); ) { addClasspathesFromDir(st.nextToken() + File.separator); } // user classpath String classPath = System.getProperty("java.class.path"); StringTokenizer st = new StringTokenizer(classPath, File.pathSeparator); while (st.hasMoreTokens()) { String path = st.nextToken(); File f = new File(path); if (!f.exists()) continue; if (path.endsWith(".jar") || path.endsWith(".zip")) htClasspath.put(f.toString(), ""); else { if (f.isDirectory()) htClasspath.put(f.toString(), ""); } } return htClasspath; } private static void addClasspathesFromDir(String dirpath) { File dir = new File(dirpath); if (dir.isDirectory()) { String[] items = dir.list(); // use list() instead of listFiles() since the latter are introduced in 1.2 for (int i = 0; i < items.length; i++) { File f = new File(dirpath + items[i]); if (!f.exists()) continue; if (items[i].endsWith(".jar") || items[i].endsWith(".zip") || items[i].endsWith(".ZIP")) { htClasspath.put(f.toString(), ""); } else if (items.equals("classes")) { if (f.isDirectory()) htClasspath.put(f.toString(), ""); } } } } /** * If name is empty, put all loadable package info into map once. */ private static void putPackageInfo(Hashtable map, String name) { String prefix = name.replace('.', '/') + "/"; Hashtable subpackages = new Hashtable(); Hashtable classes = new Hashtable(); for (Enumeration e = collectClassPath().keys(); e.hasMoreElements(); ) { String path = (String)e.nextElement(); if (path.endsWith(".jar") || path.endsWith(".zip")) appendListFromJar(subpackages, classes, path, prefix); else appendListFromFolder(subpackages, classes, path, prefix); } if (subpackages.size() > 0 || classes.size() > 0) { StringBuffer sb = new StringBuffer(1024); sb.append("{'tag':'PACKAGE','subpackages':["); for (Enumeration e = subpackages.keys(); e.hasMoreElements(); ) { sb.append("'").append(e.nextElement()).append("',"); } sb.append("],'classes':["); for (Enumeration e = classes.keys(); e.hasMoreElements(); ) { sb.append("'").append(e.nextElement()).append("',"); } sb.append("]}"); map.put(name, sb.toString()); } } public static void appendListFromJar(Hashtable subpackages, Hashtable classes, String path, String prefix) { try { for (Enumeration entries = new ZipFile(path).entries(); entries.hasMoreElements(); ) { String entry = entries.nextElement().toString(); int len = entry.length(); if (entry.endsWith(".class") && entry.indexOf('$') == -1 && entry.startsWith(prefix)) { int splitPos = entry.indexOf('/', prefix.length()); String shortname = entry.substring(prefix.length(), splitPos == -1 ? entry.length()-6 : splitPos); if (splitPos == -1) { if (!classes.containsKey(shortname)) classes.put(shortname, ""); //classes.put(shortname, "{'tag':'CLASSDEF','name':'"+shortname+"'}"); } else { if (!subpackages.containsKey(shortname)) subpackages.put(shortname, ""); //subpackages.put(shortname, "{'tag':'PACKAGE','name':'" +shortname+"'}"); } } } } catch (Throwable e) { //e.printStackTrace(); } } public static void appendListFromFolder(Hashtable subpackages, Hashtable classes, String path, String prefix) { try { String fullPath = path + "/" + prefix; File file = new File(fullPath); if (file.isDirectory()) { String[] descents = file.list(); for (int i = 0; i < descents.length; i++) { if (descents[i].indexOf('$') == -1) { if (descents[i].endsWith(".class")) { String shortname = descents[i].substring(0, descents[i].length()-6); if (!classes.containsKey(shortname)) classes.put(shortname, ""); } else if ((new File(fullPath + "/" + descents[i])).isDirectory()) { if (!subpackages.containsKey(descents[i])) subpackages.put(descents[i], ""); } } } } } catch (Throwable e) { } } private static int INDEX_PACKAGE = 0; private static int INDEX_CLASS = 1; // generate information of all packages in jar files. public static String getPackageList() { Hashtable map = new Hashtable(); for (Enumeration e = collectClassPath().keys(); e.hasMoreElements(); ) { String path = (String)e.nextElement(); if (path.endsWith(".jar") || path.endsWith(".zip")) appendListFromJar(path, map); } StringBuffer sb = new StringBuffer(4096); sb.append("{"); //sb.append("'*':'").append( map.remove("") ).append("',"); // default package for (Enumeration e = map.keys(); e.hasMoreElements(); ) { String s = (String)e.nextElement(); StringBuffer[] sbs = (StringBuffer[])map.get(s); sb.append("'").append( s.replace('/', '.') ).append("':") .append("{'tag':'PACKAGE'"); if (sbs[INDEX_PACKAGE].length() > 0) sb.append(",'subpackages':[").append(sbs[INDEX_PACKAGE]).append("]"); if (sbs[INDEX_CLASS].length() > 0) sb.append(",'classes':[").append(sbs[INDEX_CLASS]).append("]"); sb.append("},"); } sb.append("}"); return sb.toString(); } public static void appendListFromJar(String path, Hashtable map) { try { for (Enumeration entries = new ZipFile(path).entries(); entries.hasMoreElements(); ) { String entry = entries.nextElement().toString(); int len = entry.length(); if (entry.endsWith(".class") && entry.indexOf('$') == -1) { int slashpos = entry.lastIndexOf('/'); String parent = entry.substring(0, slashpos); String child = entry.substring(slashpos+1, len-6); putItem(map, parent, child, INDEX_CLASS); slashpos = parent.lastIndexOf('/'); if (slashpos != -1) { AddToParent(map, parent.substring(0, slashpos), parent.substring(slashpos+1)); } } } } catch (Throwable e) { //e.printStackTrace(); } } public static void putItem(Hashtable map, String parent, String child, int index) { StringBuffer[] sbs = (StringBuffer[])map.get(parent); if (sbs == null) { sbs = new StringBuffer[] { new StringBuffer(256), // packages new StringBuffer(256) // classes }; } if (sbs[index].toString().indexOf("'" + child + "',") == -1) sbs[index].append("'").append(child).append("',"); map.put(parent, sbs); } public static void AddToParent(Hashtable map, String parent, String child) { putItem(map, parent, child, INDEX_PACKAGE); int slashpos = parent.lastIndexOf('/'); if (slashpos != -1) { AddToParent(map, parent.substring(0, slashpos), parent.substring(slashpos+1)); } } public static String getClassInfo(String className) { Hashtable mapClasses = new Hashtable(); try { Class clazz = Class.forName(className); putClassInfo(mapClasses, clazz); } catch (Exception ex) { } if (mapClasses.size() == 1) { return mapClasses.get(className).toString(); // return {...} } else if (mapClasses.size() > 1) { StringBuffer sb = new StringBuffer(4096); sb.append("["); for (Enumeration e = mapClasses.keys(); e.hasMoreElements(); ) { String s = (String)e.nextElement(); sb.append(mapClasses.get(s)).append(","); } sb.append("]"); return sb.toString(); // return [...] } else return ""; } private static void putClassInfo(Hashtable map, Class clazz) { if (map.containsKey(clazz.getName())) return ; try { StringBuffer sb = new StringBuffer(1024); sb.append("{") .append("'tag':'CLASSDEF',").append(NEWLINE) .append("'flags':'").append(Integer.toString(clazz.getModifiers(), 2)).append("',").append(NEWLINE) .append("'name':'").append(clazz.getName().replace('$', '.')).append("',").append(NEWLINE) //.append("'package':'").append(clazz.getPackage().getName()).append("',").append(NEWLINE) // no getPackage() in JDK1.1 .append("'classpath':'1',").append(NEWLINE) .append("'fqn':'").append(clazz.getName().replace('$', '.')).append("',").append(NEWLINE); Class[] interfaces = clazz.getInterfaces(); if (clazz.isInterface()) { sb.append("'extends':["); } else { Class superclass = clazz.getSuperclass(); if (superclass != null && !"java.lang.Object".equals(superclass.getName())) { sb.append("'extends':['").append(superclass.getName().replace('$', '.')).append("'],").append(NEWLINE); putClassInfo(map, superclass); // !! } sb.append("'implements':["); } for (int i = 0, n = interfaces.length; i < n; i++) { sb.append("'").append(interfaces[i].getName().replace('$', '.')).append("',"); putClassInfo(map, interfaces[i]); // !! } sb.append("],").append(NEWLINE);; Constructor[] ctors = clazz.getConstructors(); sb.append("'ctors':["); for (int i = 0, n = ctors.length; i < n; i++) { Constructor ctor = ctors[i]; sb.append("{"); appendModifier(sb, ctor.getModifiers()); appendParameterTypes(sb, ctor.getParameterTypes()); sb.append(KEY_DESCRIPTION).append("'").append(ctors[i].toString()).append("'"); sb.append("},").append(NEWLINE); } sb.append("], ").append(NEWLINE); Field[] fields = clazz.getFields(); //java.util.Arrays.sort(fields, comparator); sb.append("'fields':["); for (int i = 0, n = fields.length; i < n; i++) { Field f = fields[i]; int modifier = f.getModifiers(); sb.append("{"); sb.append(KEY_NAME).append("'").append(f.getName()).append("',"); if (!f.getDeclaringClass().getName().equals(clazz.getName())) sb.append(KEY_DECLARING_CLASS).append("'").append(f.getDeclaringClass().getName()).append("',"); appendModifier(sb, modifier); sb.append(KEY_TYPE).append("'").append(f.getType().getName()).append("'"); sb.append("},").append(NEWLINE); } sb.append("], ").append(NEWLINE); Method[] methods = clazz.getMethods(); //java.util.Arrays.sort(methods, comparator); sb.append("'methods':["); for (int i = 0, n = methods.length; i < n; i++) { Method m = methods[i]; int modifier = m.getModifiers(); sb.append("{"); sb.append(KEY_NAME).append("'").append(m.getName()).append("',"); if (!m.getDeclaringClass().getName().equals(clazz.getName())) sb.append(KEY_DECLARING_CLASS).append("'").append(m.getDeclaringClass().getName()).append("',"); appendModifier(sb, modifier); sb.append(KEY_RETURNTYPE).append("'").append(m.getReturnType().getName()).append("',"); appendParameterTypes(sb, m.getParameterTypes()); sb.append(KEY_DESCRIPTION).append("'").append(m.toString()).append("'"); sb.append("},").append(NEWLINE); } sb.append("], ").append(NEWLINE); Class[] classes = clazz.getClasses(); sb.append("'classes': ["); for (int i = 0, n = classes.length; i < n; i++) { Class c = classes[i]; sb.append("'").append(c.getName().replace('$', '.')).append("',"); putClassInfo(map, c); // !! } sb.append("], ").append(NEWLINE); appendDeclaredMembers(map, clazz, sb); sb.append("}"); map.put(clazz.getName(), sb); } catch (Exception ex) { //ex.printStackTrace(); } } private static void appendDeclaredMembers(Hashtable map, Class clazz, StringBuffer sb) { Constructor[] ctors = clazz.getDeclaredConstructors(); sb.append("'declared_ctors':["); for (int i = 0, n = ctors.length; i < n; i++) { Constructor ctor = ctors[i]; if (!Modifier.isPublic(ctor.getModifiers())) { sb.append("{"); appendModifier(sb, ctor.getModifiers()); appendParameterTypes(sb, ctor.getParameterTypes()); sb.append(KEY_DESCRIPTION).append("'").append(ctors[i].toString()).append("'"); sb.append("},").append(NEWLINE); } } sb.append("], ").append(NEWLINE); Field[] fields = clazz.getDeclaredFields(); sb.append("'declared_fields':["); for (int i = 0, n = fields.length; i < n; i++) { Field f = fields[i]; int modifier = f.getModifiers(); if (!Modifier.isPublic(modifier)) { sb.append("{"); sb.append(KEY_NAME).append("'").append(f.getName()).append("',"); if (!f.getDeclaringClass().getName().equals(clazz.getName())) sb.append(KEY_DECLARING_CLASS).append("'").append(f.getDeclaringClass().getName()).append("',"); appendModifier(sb, modifier); sb.append(KEY_TYPE).append("'").append(f.getType().getName()).append("'"); sb.append("},").append(NEWLINE); } } sb.append("], ").append(NEWLINE); Method[] methods = clazz.getDeclaredMethods(); sb.append("'declared_methods':["); for (int i = 0, n = methods.length; i < n; i++) { Method m = methods[i]; int modifier = m.getModifiers(); if (!Modifier.isPublic(modifier)) { sb.append("{"); sb.append(KEY_NAME).append("'").append(m.getName()).append("',"); if (!m.getDeclaringClass().getName().equals(clazz.getName())) sb.append(KEY_DECLARING_CLASS).append("'").append(m.getDeclaringClass().getName()).append("',"); appendModifier(sb, modifier); sb.append(KEY_RETURNTYPE).append("'").append(m.getReturnType().getName()).append("',"); appendParameterTypes(sb, m.getParameterTypes()); sb.append(KEY_DESCRIPTION).append("'").append(m.toString()).append("'"); sb.append("},").append(NEWLINE); } } sb.append("], ").append(NEWLINE); Class[] classes = clazz.getDeclaredClasses(); sb.append("'declared_classes': ["); for (int i = 0, n = classes.length; i < n; i++) { Class c = classes[i]; if (!Modifier.isPublic(c.getModifiers())) { sb.append("'").append(c.getName().replace('$', '.')).append("',"); putClassInfo(map, c); // !! } } sb.append("], ").append(NEWLINE); } private static void appendModifier(StringBuffer sb, int modifier) { sb.append(KEY_MODIFIER).append("'").append(Integer.toString(modifier, 2)).append("', "); } private static void appendParameterTypes(StringBuffer sb, Class[] paramTypes) { if (paramTypes.length == 0) return ; sb.append(KEY_PARAMETERTYPES).append("["); for (int j = 0; j < paramTypes.length; j++) { sb.append("'").append(paramTypes[j].getName()).append("',"); } sb.append("],"); } private static boolean isBlank(String str) { int len; if (str == null || (len = str.length()) == 0) return true; for (int i = 0; i < len; i++) if ((Character.isWhitespace(str.charAt(i)) == false)) return false; return true; } // test methods static void debug(String s) { if (debug_mode) System.out.println(s); } static void output(String s) { if (!debug_mode) System.out.print(s); } private static void usage() { System.out.println("Reflection for javacomplete (" + VERSION + ")"); System.out.println(" java [-classpath] Reflection [-c] [-d] [-e] [-h] [-v] [-p] [-s] name[,comma_separated_name_list]"); System.out.println("Options:"); System.out.println(" -a list all members in alphabetic order"); System.out.println(" -c list constructors"); System.out.println(" -C return class info"); System.out.println(" -d default strategy, i.e. instance fields, instance methods, static fields, static methods"); System.out.println(" -e check class existed"); System.out.println(" -E check class existed and read class information"); System.out.println(" -D debug mode"); System.out.println(" -p list package content"); System.out.println(" -P print all package info in the Vim dictionary format"); System.out.println(" -s list static fields and methods"); System.out.println(" -h help"); System.out.println(" -v version"); } public static void main(String[] args) { String className = null; int option = 0x0; boolean wholeClassInfo = false; boolean onlyStatic = false; boolean onlyConstructor = false; boolean listPackageContent = false; boolean checkExisted = false; boolean checkExistedAndRead = false; boolean allPackageInfo = false; for (int i = 0, n = args.length; i < n && !isBlank(args[i]); i++) { //debug(args[i]); if (args[i].charAt(0) == '-') { if (args[i].length() > 1) { switch (args[i].charAt(1)) { case 'a': break; case 'c': // request constructors option = option | OPTION_CONSTRUCTOR; onlyConstructor = true; break; case 'C': // class info wholeClassInfo = true; break; case 'd': // default strategy option = option | STRATEGY_DEFAULT; break; case 'D': // debug mode debug_mode = true; break; case 'e': // class existed checkExisted = true; break; case 'E': // check existed and read class information checkExistedAndRead = true; break; case 'h': // help usage(); return ; case 'v': // version System.out.println("Reflection for javacomplete (" + VERSION + ")"); break; case 'p': listPackageContent = true; break; case 'P': option = RETURN_ALL_PACKAGE_INFO; break; case 's': // request static members option = option | OPTION_STATIC_METHOD | OPTION_STATIC_FIELD; onlyStatic = true; break; default: } } } else { className = args[i]; } } if (className == null && (option & RETURN_ALL_PACKAGE_INFO) != RETURN_ALL_PACKAGE_INFO) { return; } if (option == 0x0) option = OPTION_INSTANCE; if (wholeClassInfo) output( getClassInfo(className) ); else if ((option & RETURN_ALL_PACKAGE_INFO) == RETURN_ALL_PACKAGE_INFO) output( getPackageList() ); else if (checkExistedAndRead) output( existedAndRead(className) ); else if (checkExisted) output( String.valueOf(existed(className)) ); else if (listPackageContent) output( getPackageList(className) ); } }