/* * * Completion.java * Copyright (C) 1999, 2001, 2002, 2003 Rodrigo Reyes (reyes@chez.com) * * $Revision: 1.23 $ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package jde.util; import java.lang.reflect.Modifier; import java.lang.reflect.Field; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.io.Writer; import java.io.BufferedWriter; import java.io.OutputStreamWriter; import java.io.IOException; /** * This class provides completion facilities. * * @author Rodrigo Reyes (reyes@chez.com) */ public class Completion { /************************************************************************* * Constants *************************************************************************/ public static final String NIL = "nil"; public static final String NL = "\n"; public static final String T = "t"; public static final String LIST = "list"; public static final String START_PAREN = "("; public static final String END_PAREN = ")"; public static final String DOUBLE_QUOTE = "\""; public static final String SPACE = " "; public static final String START_LIST; static { StringBuffer sb = new StringBuffer (10); sb.append(START_PAREN); sb.append(LIST); sb.append(SPACE); START_LIST = sb.toString(); } /** * Access level for a class member */ public static final int PUBLIC = 0; /** * Access level for a class member */ public static final int PROTECTED = 1; /** * Access level for a class member */ public static final int DEFAULT = 2; /** * Access level for a class member */ public static final int PRIVATE = 3; /** * Tests whether a class is an ancestor of another class. * This method prints "t" to standard out if the class is an ancestor * of the other class. If the class is not an ancestor or either class * cannot be found, this method prints nil to standard out. * @param ancestor Name of the ancestor class * @param child Name of the supposed child class */ public static void isAncestorOf(String ancestor, String child) { try { Class classAncestor = Class.forName(ancestor); Class classChild = Class.forName(child); if (classAncestor.isAssignableFrom(classChild)) { System.out.println(T); } else { System.out.println(NIL); } } catch (Exception ex) { System.out.println(NIL); } } /** * Returns true if the entity is accessible to the specified level of * protection. * @param modifiers the modifiers as returned by Member.getModifiers() * or Class.getModifiers() * @param level the level of protection * @return if the Member is accessible to the specified level of * protection. * @see Member.getModifiers() * @see Class.getModifiers() */ private static boolean isAccessible(int modifiers, int level) { switch(level) { case PUBLIC: // member is accessible if it has public access return Modifier.isPublic (modifiers); case PROTECTED: // accessible if member is protected return Modifier.isProtected (modifiers); case DEFAULT: // accessible if member is not public, protected // or private return (!Modifier.isPublic (modifiers) && !Modifier.isProtected(modifiers) && !Modifier.isPrivate (modifiers)); case PRIVATE: // accessible if member is private return Modifier.isPrivate (modifiers); default: // cannot get here any more, since the access level is // only used internally throw new Error("Completion.isAccessible(int, int) " + "called with incorrect access level parameter:" + level); }//switch } /** * Prints (list "name" "type") to the system output. * * @param name field name * @param type field type */ private static String printField(String name, String type) { StringBuffer sb = new StringBuffer (30); sb.append(START_LIST); sb.append(printWithinQuotes(name)); sb.append(SPACE); sb.append(printWithinQuotes(type)); sb.append(END_PAREN); return sb.toString(); } /** * Prints (list "name" "params") to the system output. * * @param name constructor name * @param params parameter type */ private static String printConstructor(String name, Class[] params) { StringBuffer sb = new StringBuffer (30); sb.append(START_LIST); sb.append(printWithinQuotes(name)); sb.append(SPACE); sb.append(listClassArray(params)); sb.append(SPACE); return sb.toString(); } /** * Prints (list "name" "returnType" "args") to the system output. * * @param name method name * @param returnType method return type * @param args method arguments */ private static String printMethod(String name, String returnType, Class[] args) { StringBuffer sb = new StringBuffer (30); sb.append(START_LIST); sb.append(printWithinQuotes(name)); sb.append(SPACE); sb.append(printWithinQuotes(returnType)); sb.append(SPACE); sb.append(listClassArray(args)); sb.append(SPACE); return sb.toString(); } /** * Prints (list "className") to the system output. * * @param name className */ private static String printClass(String name) { StringBuffer sb = new StringBuffer (30); sb.append(START_LIST); sb.append(printWithinQuotes(name)); sb.append(END_PAREN); return sb.toString(); } /** * printExceptions * * @param exceptions a Class[] * @return a String */ private static String printExceptions(Class[] exceptions) { StringBuffer sb = new StringBuffer (30); sb.append(START_LIST); sb.append(listClassArray(exceptions)); sb.append(END_PAREN); return sb.toString(); } /** * Prints item within quotes i.e "item" * * @param item string to be quoted. */ private static String printWithinQuotes(String item) { StringBuffer sb = new StringBuffer (30); sb.append(DOUBLE_QUOTE); sb.append(item); sb.append(DOUBLE_QUOTE); return sb.toString(); } /** * Recursively finds fields of the specified access level in the * supplied class and superclasses. * * @param c the class to start the search in - nothing is done if * this is NULL * @param level the access level to look for * @param sb the StringBuffer where the results should be added */ private static void recursiveListFields(Class c, int level, StringBuffer sb) { //This is only used while initializing if (c == null) { return; } Field[] fields; Field field; String f; // ----- Added by Petter for interfaces if (level == PUBLIC) { // For public access, we can use getFields() and skip // recursion. This ensures that the method works for // interface types, and saves some function calls. fields = c.getFields(); } else { fields = c.getDeclaredFields(); } // ----- End addition by Petter for (int index = 0; index < fields.length ; index++) { field = fields[index]; if (isAccessible(field.getModifiers(), level)) { f = printField(field.getName(), className(field.getType())); if (sb.toString().lastIndexOf(f) == -1) { sb.append(f); } } } // ----- Addition by Petter to reduce the number of function // calls and not list non-accessible private fields. if (!c.isInterface() && level != PRIVATE && level != PUBLIC) { // For interfaces, the getSuperClass() method will return // nil, and in any case, the only type of access relevant // for interfaces is PUBLIC. For PUBLIC access, the // getFields() call has listed all the relevant fields. // For PRIVATE access, that is only applicable in the // calling class anyway, so we shouldn't do recursion. recursiveListFields(c.getSuperclass(), level, sb); } // ----- End addition by Petter } /** * Finds constructors of the specified access level in the supplied class. * * @param c the class to search in * @param level the access level to look for * @param sb the StringBuffer where the results should be added */ private static void listConstructors(Class c, int level, StringBuffer sb) { Constructor[] constrs = c.getDeclaredConstructors(); Constructor constructor; Class[] exceptions; StringBuffer cons; for (int index = 0; index < constrs.length; index++) { constructor = constrs[index]; if (isAccessible(constructor.getModifiers(), level)) { cons = new StringBuffer(100); cons.append(printConstructor(constructor.getName(), constructor.getParameterTypes())); // Add exceptions exceptions = constructor.getExceptionTypes(); if (exceptions.length > 0) { cons.append(printExceptions(exceptions)); } else { cons.append(NIL); } cons.append(END_PAREN); if (sb.toString().lastIndexOf(cons.toString()) == -1) { sb.append(cons); } } } } /** * Recursively finds methods of the specified access level in the * supplied class and superclasses. * * @param c the class to start the search in - nothing is done if this is * NULL * @param level the access level to look for * @param sb the StringBuffer where the results should be added */ private static void recursiveListMethods(Class c, int level, StringBuffer sb) { //This is only used while initializing if (c == null) { return; } Method[] methods; Method method; Class[] exceptions; StringBuffer temp; // ----- Added by Petter for interfaces if (level == PUBLIC) { // For public access, we can use getMethods() and skip // recursion. This ensures that the method works for // interface types, and saves some function calls. methods = c.getMethods(); } else { methods = c.getDeclaredMethods(); } // ----- End addition by Petter for (int index = 0; index < methods.length ; index++) { method = methods[index]; if (isAccessible(method.getModifiers(), level)) { temp = new StringBuffer(100); temp.append(printMethod(method.getName(), className(method.getReturnType()), method.getParameterTypes())); // Add exceptions exceptions = method.getExceptionTypes(); if (exceptions.length > 0) { temp.append(printExceptions(exceptions)); } else { temp.append(NIL); } temp.append(END_PAREN); if (sb.toString().lastIndexOf(temp.toString()) == -1) { sb.append(temp); } } } // ----- Addition by Petter to reduce the number of function // calls and not list non-accessible private fields. if (!c.isInterface() && level != PRIVATE && level != PUBLIC) { // For interfaces, the getSuperClass() method will return // nil, and in any case, the only type of access relevant // for interfaces is PUBLIC. For PUBLIC access, the // getMethods() call has listed all the relevant members. // For PRIVATE access, that is only applicable in the // calling class anyway, so we shouldn't do recursion. recursiveListMethods(c.getSuperclass(), level, sb); } // ----- End addition by Petter } /** * Recursively finds inner classes of the specified access level * in the supplied class and superclasses. * * @param c the class to start the search in - nothing is done if this is * NULL * @param level the access level to look for * @param sb the StringBuffer where the results should be added */ private static void recursiveListInnerClasses(Class c, int level, StringBuffer sb) { if (c == null) { return; } Class[] innerClasses; Class innerClass; String clas; // ----- Added by Petter for interfaces if (level == PUBLIC) { // For public access, we can use getClasses() and skip // recursion. This ensures that the method works for // interface types, and saves some function calls. XXX - // actually, this doesn't work properly, since classes // defined in interfaces don't show up here. innerClasses = c.getClasses(); } else { innerClasses = c.getDeclaredClasses(); } // ----- End addition by Petter for (int index = 0; index < innerClasses.length ; index++) { innerClass = innerClasses[index]; if (isAccessible(innerClass.getModifiers(), level)) { clas = printClass(innerClass.getName()); if (sb.toString().lastIndexOf(clas) == -1) { sb.append(clas); } } } // ----- Addition by Petter to reduce the number of function // calls and not list non-accessible private fields. if (!c.isInterface() && level != PRIVATE && level != PUBLIC) { // For interfaces, the getSuperClass() method will return // nil, and in any case, the only type of access relevant // for interfaces is PUBLIC. For PUBLIC access, the // getClasses() call has listed all the relevant members. // For PRIVATE access, that is only applicable in the // calling class anyway, so we shouldn't do recursion. recursiveListInnerClasses(c.getSuperclass(), level, sb); } // ----- End addition by Petter } private static void listClassInfo(Class c, int level, StringBuffer sb) { sb.append(START_LIST); //we add the protected/private fields depending on the access level sb.append(START_LIST); recursiveListFields(c, level, sb); sb.append(END_PAREN); // constructors sb.append(NL); sb.append(START_LIST); listConstructors(c, level, sb); sb.append(END_PAREN); // methods, added recursively sb.append(NL); sb.append(START_LIST); recursiveListMethods(c, level, sb); sb.append(END_PAREN); // inner classes, added recursively sb.append(NL); sb.append(START_LIST); recursiveListInnerClasses(c, level, sb); sb.append(END_PAREN); sb.append(END_PAREN); sb.append(NL); return; } /** * Gets information on the specified class. Information is returned as a * list of lists that is printed to System.out. * * @param className a String value */ public static void getClassInfo(String className) { getClassInfo(className, PUBLIC); } /** * Gets information on the specified class. Information is returned as a * list of lists that is printed to System.out. * * @param className a String value * @param level access level i.e. public, protected, default, and private */ public static void getClassInfo(String className, int level) { try { DynamicClassLoader dcl = new DynamicClassLoader(); Class c = dcl.loadClass(className); if (c != null) { StringBuffer sb = new StringBuffer (3000); sb.append(START_LIST); sb.append(NL); listClassInfo(c, level, sb); sb.append(END_PAREN); sb.append(NL); Writer out = new BufferedWriter(new OutputStreamWriter(System.out)); try { out.write(sb.toString()); out.flush(); } catch (IOException e) { } } } catch (ClassNotFoundException e) { System.out.println(NIL); } catch (Exception e) { System.out.println("(error \"Trying to load " + className + " caused a Java exception: " + e + "\")"); } catch (NoClassDefFoundError e) { System.out.println(NIL); } catch (UnsatisfiedLinkError e) { // This occurs with classes that have native methods whose native // implementations cannot be found. System.out.println("(error \"Trying to load " + className + " caused a Java UnsatisfiedLinkError: " + e + "\")"); } catch (LinkageError e) { System.out.println("(error \"Trying to load " + className + " caused a Java LinkageError: " + e + "\")"); } } /** * Looks up an unqualified class name in the class path to find possible * fully qualified matches. * * @param className a value of type 'String' * @param imports an array of imported packages */ public static void getClassInfo(String className, String[]imports) { String name; Class c; for (int i = 0 ; i < imports.length ; i++) { name = imports[i] + className; try { c = Class.forName(name); if (c != null) { getClassInfo(name); } } catch (ClassNotFoundException e) { // try to find className in another package. } catch (Exception e) { System.out.println("(error \"Trying to load " + name + " caused a Java exception: " + e + "\")"); } catch (NoClassDefFoundError e) { System.out.println(NIL); } catch (UnsatisfiedLinkError e) { // This occurs with classes that have native methods whose native // implementations cannot be found. System.out.println("(error \"Trying to load " + name + " caused a Java UnsatisfiedLinkError: " + e + "\")"); } catch (LinkageError e) { System.out.println("(error \"Trying to load " + name + " caused a Java LinkageError: " + e + "\")"); } } System.out.println(NIL); } static String className(Class c) { if (c.isArray()) return c.getComponentType().getName() + "[]"; else return c.getName(); } static String listClassArray(Class[] classes) { StringBuffer sb = new StringBuffer (100); for (int i = 0; i < classes.length; i++) { sb.append(printWithinQuotes(className(classes[i]))); if ((i + 1) != classes.length) { sb.append(SPACE); } } return sb.toString(); } public static void main (String[] args) { getClassInfo("java.lang.Object", 0); getClassInfo("java.lang.Object", 1); getClassInfo("java.lang.Object", 2); getClassInfo("java.lang.Object", 3); } } // Completion /* * $Log: Completion.java,v $ * Revision 1.23 2003/07/25 04:40:54 paulk * Return NIL for NoClassDefFoundError. * * Revision 1.22 2003/07/25 04:26:09 paulk * Revert to outputting nil for class not found exception. * * Revision 1.21 2003/07/25 04:10:18 paulk * Trigger a Lisp error if trying to load a class causes a Java error or * exception. * * Revision 1.20 2002/02/21 12:23:57 jslopez * Rollback to create a new instance of the * DynamicClassLoader all the time. * * Revision 1.19 2002/02/21 02:59:09 jslopez * Updates the classInfoClass to reuse a single instance of the * DynamicClassLoader. * * Revision 1.18 2002/02/20 12:34:39 jslopez * Updates getClassInfo to catch Exception. * * Revision 1.17 2002/01/31 02:52:17 jslopez * changes: * - use getFields(), etc., for all public access: saves recursion, and ensures * that we get the members from the interfaces, without needing to go through * each of them. * - do not do recursion for PUBLIC/PRIVATE access, or for interfaces. Saves * function calls, and fixes a bug that meant listing non-accessible private * members. * Thanks to Petter Måhlén for contributing these changes. * * Revision 1.16 2001/11/18 02:40:01 jslopez * Cleaned up imports. * * Revision 1.15 2001/10/03 05:53:42 paulk * Replaces calls to sb.lastIndexof(...) where sb is a StringBuffer that does * not define the method lastIndexOf with calls to sb.toString().lastIndexOf(...). * * Revision 1.14 2001/09/28 13:35:05 jslopez * Now getClassInfo retreives protected, and default package info * for super classes. Thanks to Petter Måhlén [petter.mahlen@chello.se] * * Revision 1.13 2001/09/09 14:30:07 jslopez * Removed inecessary isPublic called in several places inside listClassInfo. * Added more Inner class info to the listClassInfo method. * * Revision 1.12 2001/09/06 14:52:19 jslopez * Removing debugging comment from the completion. * * Revision 1.11 2001/09/02 03:37:04 jslopez * Moved all hardcoded strings to static final variables. * Joined the two listClassInfo methods. * Substituted all the System.out.print for the use of a StringBuffer. The result * is print out at the end of the listClassInfo method. * * Revision 1.10 2001/08/14 05:15:02 paulk * Miscellaneous updates. * * Revision 1.9 2001/07/21 03:56:23 paulk * Addional inner class support. Thanks to Javier Lopez. * * Revision 1.8 2001/07/21 03:42:24 paulk * Now includes inner classes in the class info list. Thanks to Javier Lopez. * * Revision 1.7 2001/07/06 02:00:09 paulk * Makefile * * Revision 1.6 2001/06/27 03:09:04 paulk * The isAccessible method now returns true for both private and protected methods * when the access modifier is private. Thanks to "Javier Lopez" . * * Revision 1.5 2000/08/10 08:46:25 paulk * Now outputs nil if class info not found. * * Revision 1.4 2000/08/01 07:44:49 paulk * Adds an isAncestorOf method. Now gets private and protected methods and fields. Thanks to Stephan Nicolas. * * Revision 1.3 2000/07/27 04:49:52 paulk * Now returns the type as well as name of each public field of a class. Thanks to Stephane Nicolas . * * Revision 1.2 2000/02/09 04:48:41 paulk * Now uses Modifier.isPublic() to test whether a class's fields, * methods, and constructors are public and hence candidates for * completion. Now gets all fields and methods, not just those declared * by the class. * */ // End of Completion.java