如何通过类型签名搜索Java API方法?

时间:2010-11-15 21:19:59

标签: java search reference navigation static-analysis

是否有任何可用的开源工具支持通过参数类型集和返回类型搜索Java方法?

作为一个例子,假设我正在寻找一种为int数组生成哈希码的方法。我搜索一个采用int []参数并返回int:

的方法
int[] -> int

产生

java.util.Arrays#hashCode(int[])
...

或者我可能想要找到一个方法,它接受一个字符串,并替换字符,并用字符替换它。所以我搜索匹配的方法:

String, char, char -> String

产生

java.lang.String#replace(char, char)
...

理想情况下,我想要一个等同于Haskell Hoogle的Java,它支持按类型签名搜索函数。

我希望该工具能够:

  • 忽略参数的顺序
  • 包括接受'更宽'类型作为参数的方法(例如超类)
  • 包括将'narrower'类型作为返回值(例如子类)返回的方法
  • 将'self'值视为实例方法的参数(例如'String - > int'将包含String#hashCode)

我知道许多IDE支持搜索获取或返回给定类型的方法,但尚未看到通过参数类型组合返回类型缩小搜索范围的工具

3 个答案:

答案 0 :(得分:14)

解决方案评论:

  1. 忽略顺序,测试参数的所有排列,直到找到给定函数的匹配为止
  2. 原始类型是可互换的(例如Integer.class = Integer.TYPE
  3. 检查更广泛的参数
  4. 返回类型可以更窄
  5. 自我被视为findMethod方法
  6. 参数的第一

    这是程序的输出:

    int[] -> Integer
        public native int java.lang.Object.hashCode()
        public static native int java.lang.reflect.Array.getLength(java.lang.Object) throws java.lang.IllegalArgumentException
        public static int java.util.Arrays.hashCode(int[])
        public static native int java.lang.System.identityHashCode(java.lang.Object)
    
    String, Character, Character -> String
        public java.lang.String java.lang.String.replace(char,char)
    
    String -> Integer
        public int java.lang.String.hashCode()
        public int java.lang.String.length()
        public static native int java.lang.reflect.Array.getLength(java.lang.Object) throws java.lang.IllegalArgumentException
        public static java.lang.Integer java.lang.Integer.decode(java.lang.String) throws java.lang.NumberFormatException
        public static java.lang.Integer java.lang.Integer.valueOf(java.lang.String) throws java.lang.NumberFormatException
        public static int java.lang.Integer.parseInt(java.lang.String) throws java.lang.NumberFormatException
        public static java.lang.Integer java.lang.Integer.getInteger(java.lang.String)
        public static native int java.lang.System.identityHashCode(java.lang.Object)
    
    List -> Void
        public abstract void java.util.List.clear()
        public static void java.util.concurrent.locks.LockSupport.park(java.lang.Object)
        public static void java.util.Collections.reverse(java.util.List)
        public static void java.util.Collections.shuffle(java.util.List)
        public static void java.util.Collections.sort(java.util.List)
    

    代码:

    public class MethodMatcher {
    
        public static void main(String... args) throws Exception {
    
            // where to load some classes from (could be a list of classes to 
            // search from)..
            //        String pathToJar = "/usr/lib/jvm/java-6-sun-1.6.0.22/jre/lib/rt.jar";
            String pathToJar = "C:\\Program Files\\Java\\jdk1.6.0_20\\jre\\lib\\rt.jar";
    
            MethodMatcher m = new MethodMatcher(pathToJar, 
                    "java.io", "java.lang", "java.math", "java.net", 
                    "java.nio", "java.text", "java.util");
    
            // print some examples
            m.printExampleSearch(Integer.class, new int[0].getClass());
            m.printExampleSearch(String.class, String.class, Character.class, Character.class);
            m.printExampleSearch(Integer.class, String.class);
            m.printExampleSearch(Void.class, List.class);
        }
    
        public void printExampleSearch(Class<?> returnType, Class<?>... arguments) {
    
            for (int i = 0; i < arguments.length; i++)
                System.out.print((i == 0 ? "":", ") + arguments[i].getSimpleName());
    
            System.out.println(" -> " + returnType.getSimpleName());
    
            Set<Method> methods = findMethods(returnType, arguments);
    
            for (Method method : methods)
                System.out.println("\t" + method);
    
            System.out.println();
        }
    
    
    
        private final List<MethodFinder> klasses;
    
        public MethodMatcher(String jarFile, String... allowedPackages) 
        throws IOException, ClassNotFoundException {
    
            klasses = loadClasses(jarFile, allowedPackages);
        }
    
        /**
         * Finds a set of methods
         * @param returnType the return type
         * @param arguments the arguments (in any order)
         * @return a set of methods
         */
        public Set<Method> findMethods(Class<?> returnType,
                Class<?>... arguments) {
    
            Set<Method> methods = new LinkedHashSet<Method>();
    
            if (arguments.length > 0) {
                MethodFinder instance = new MethodFinder(arguments[0]);
    
                Class<?>[] rest = new Class<?>[arguments.length - 1];
                System.arraycopy(arguments, 1, rest, 0, rest.length);
    
                methods.addAll(instance.findInstanceMethods(returnType, rest));
            }
            else {
                for (MethodFinder k : klasses)
                    methods.addAll(k.findInstanceMethods(returnType, arguments));
            }
    
            for (MethodFinder k : klasses)
                methods.addAll(k.findStaticMethods(returnType, arguments));
    
            return methods;
        }
    
        /**
         * A method finder class
         */
        static class MethodFinder {
    
            public final Class<?> klass;
    
            /**
             * Constructs the method finder (doh)
             * @param klass the class
             */
            public MethodFinder(Class<?> klass) {
                this.klass = klass;
            }
    
            /**
             * Finds instance method matches
             * @param returnType the return type
             * @param arguments the arguments (in any order)
             * @return
             */
            public List<Method> findInstanceMethods(Class<?> returnType, 
                    Class<?>... arguments) {
    
                List<Method> matches = new LinkedList<Method>();
    
                for (Method method : klass.getMethods()) {
                    if ((method.getModifiers() & Modifier.STATIC) == 0) 
                        if (testMethod(method, returnType, arguments))
                            matches.add(method);
                }
    
                return matches;        
            }
    
            /**
             * Finds static method matches
             * @param returnType the return type
             * @param arguments the arguments (in any order)
             * @return
             */
            public List<Method> findStaticMethods(Class<?> returnType,
                    Class<?>... arguments) {
    
                List<Method> matches = new LinkedList<Method>();
    
                for (Method method : klass.getMethods()) 
                    if ((method.getModifiers() & Modifier.STATIC) != 0) 
                        if (testMethod(method, returnType, arguments))
                            matches.add(method);
    
                return matches;        
            }
    
            /**
             * Tests a method if it is a match
             * @param method the method to test
             * @param returnType the return type
             * @param arguments the arguments (in any order)
             * @return true if it matches
             */
            private boolean testMethod(Method method, 
                    Class<?> returnType, 
                    Class<?>... arguments) {
    
                boolean returnTypeIsOk = false;
                for (Class<?> ic : getInterchangable(returnType))
                    if (ic.isAssignableFrom(method.getReturnType()))
                        returnTypeIsOk = true;
    
                if (!returnTypeIsOk)
                    return false;
    
                Class<?>[] methodArguments = method.getParameterTypes();
    
                if (methodArguments.length != arguments.length)
                    return false;
    
                if (methodArguments.length == 0) {
                    return true;
                }
                else {
                    Permutations permutations = new Permutations(arguments);
    
                    outer: for (Class<?>[] permutation : permutations) {
                        for (int i = 0; i < methodArguments.length; i++) {
    
                            boolean canAssign = false;
                            for (Class<?> ic : getInterchangable(permutation[i])) 
                                if (methodArguments[i].isAssignableFrom(ic))
                                    canAssign = true;
    
                            if (!canAssign)
                                continue outer;
                        }
                        return true;
                    }
    
                    return false;
                }
            }
    
            /**
             * Returns the autoboxing types
             * @param type the type to autobox :)
             * @return a list of types that it could be
             */
            private static Class<?>[] getInterchangable(Class<?> type) {
    
                if (type == Boolean.class || type == Boolean.TYPE)
                    return new Class<?>[] { Boolean.class, Boolean.TYPE };
                if (type == Character.class || type == Character.TYPE)
                    return new Class<?>[] { Character.class, Character.TYPE };
                if (type == Short.class || type == Short.TYPE)
                    return new Class<?>[] { Short.class, Short.TYPE };
                if (type == Integer.class || type == Integer.TYPE)
                    return new Class<?>[] { Integer.class, Integer.TYPE };
                if (type == Float.class || type == Float.TYPE)
                    return new Class<?>[] { Float.class, Float.TYPE };
                if (type == Double.class || type == Double.TYPE)
                    return new Class<?>[] { Double.class, Double.TYPE };
                if (type == Void.class || type == Void.TYPE)
                    return new Class<?>[] { Void.class, Void.TYPE };
    
                return new Class<?>[] { type };
            }
    
    
            /**
             * Creates a permutation list of all different combinations
             */
            @SuppressWarnings("serial")
            private class Permutations extends LinkedList<Class<?>[]> {
    
                /**
                 * Creates a permutation list
                 * @param list the list to be permutated
                 */
                public Permutations(Class<?>[] list) {
                    permutate(new LinkedList<Class<?>>(Arrays.asList(list)),
                            new LinkedList<Class<?>>());
                }
    
                // ugly, there is better ways of doing this...
                private void permutate(List<Class<?>> tail, List<Class<?>> choosen) {
    
                    if (tail.isEmpty()) {
                        add(choosen.toArray(new Class<?>[0]));
                        return;
                    }
    
                    ListIterator<Class<?>> it = tail.listIterator();
    
                    while (it.hasNext()) {
    
                        Class<?> current = it.next();
    
                        choosen.add(current);
                        it.remove();
    
                        permutate(new LinkedList<Class<?>>(tail), choosen);
    
                        choosen.remove(current);
                        it.add(current);
                    }
                }
            }
        }
    
        /**
         * A hack to read some classes from some allowed packages
         * @param jarFile the jar file to read from
         * @param allowedPackages the allowed packages
         * @return a list of MethodFinders
         * @throws IOException
         * @throws ClassNotFoundException
         */
        private static List<MethodFinder> loadClasses(
                String jarFile, 
                String... allowedPackages) throws IOException, ClassNotFoundException {
    
            List<MethodFinder> klasses = new LinkedList<MethodFinder>();
    
            JarFile file = new JarFile(jarFile);
            try {
                Enumeration<JarEntry> enumerator = file.entries();
    
                while (enumerator.hasMoreElements()) {
    
                    String name = enumerator.nextElement().getName();
    
                    if (!name.endsWith(".class")) 
                        continue;
    
                    name = name.substring(0, name.length() - 6).replace('/', '.');
    
                    boolean allowed = false;
                    for (String pkg : allowedPackages)
                        allowed |= name.startsWith(pkg);
    
                    if (allowed)
                        klasses.add(new MethodFinder(Class.forName(name)));
                }
            } 
            finally {
                if (file != null)
                    file.close();
            }
    
            return klasses;
        }
    }
    

答案 1 :(得分:9)

我刚刚在Eclipse(Helios)上试过这个,它支持这个。按Ctrl-H,转到Java选项卡,选择Search For - &gt;方法单选按钮,限制到 - &gt;声明。在搜索字符串中输入*(int, int) int,它将返回大量带有两个整数并返回int的方法结果。

答案 2 :(得分:-4)

非常好的问题虽然我不明白为什么你需要这样的工具。 我很遗憾地说,但似乎实现这样的工具比写这篇文章花费的时间更少。 这是我刚刚实现的代码。花了182秒。它是一个静态方法,它接受类,返回类型和参数,并返回与签名匹配的类的所有方法。

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class ClassUtil {
    public static Method[] getMethodsBySignature(Class<?> clazz, Class<?> returnType, Class<?>... args) {
        List<Method> result = new ArrayList<Method>();
        for (Method m : clazz.getDeclaredMethods()) {
            if (m.getReturnType().equals(returnType)) {
                Class<?>[] params = m.getParameterTypes();
                if (Arrays.equals(params, args)) {
                    result.add(m);
                }
            }
        }
        return result.toArray(new Method[result.size()]);
    }
}

您可以再花5到10分钟来实现打开jar的方法,遍历条目,调用Class.forName()然后调用我的方法。就是这样!