rc/vim/autoload/Reflection.java

671 lines
23 KiB
Java
Raw Normal View History

/**
* Reflection.java
*
* A utility class for javacomplete mainly for reading class or package information.
* Version: 0.77
* Maintainer: cheng fang <fangread@yahoo.com.cn>
* 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) );
}
}