如何通过类型签名搜索 Java API 方法? [英] How to search for Java API methods by type signature?

查看:36
本文介绍了如何通过类型签名搜索 Java API 方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Are there any open-source tools available which support searching for Java methods by the set of parameter types and return type?

举个例子,假设我正在寻找一种为整数数组生成哈希码的方法.我搜索了一个接受 int[] 参数并返回一个 int 的方法:

As an example, say I'm looking for a method to generate a hash code for an array of ints. I search for a method which takes an int[] parameter and returns an int:

int[] -> int

屈服

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

或者我可能想找到一种方法,它接受一个字符串、要替换的字符以及要替换的字符.所以我搜索了一个匹配的方法:

Or I may want to find a method which takes a String, and character to replace, and the character to replace it with. So I search for a matching method:

String, char, char -> String

屈服

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

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

Ideally I'd like a Java equivalent to Haskell's Hoogle, which supports searching for functions by type signature.

我希望该工具能够:

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

我知道许多 IDE 支持搜索采用或返回给定类型的方法,但还没有看到通过参数类型返回类型的组合来缩小搜索范围的工具.

I'm aware that many IDEs support searching for methods which take or return a given type, but haven't yet seen a tool to narrow the search by the combination of parameter types and return type.

推荐答案

解决方案评论:

  1. 忽略顺序,测试所有参数排列,直到找到给定函数的匹配项
  2. 原始类型是可以互换的(例如 Integer.class = Integer.TYPE)
  3. 检查更广泛的参数
  4. 返回类型可以更窄
  5. Self 被视为 findMethod 方法的第一个参数
  1. Ignoring order, all permutation of parameters are tested till a match of a given function is found
  2. Primitive types are interchangeable (eg Integer.class = Integer.TYPE)
  3. Checks wider parameters
  4. Return types can be narrower
  5. Self is treated as first of the arguments to the findMethod method

<小时>

这是程序的输出:

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("	" + 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;
    }
}

这篇关于如何通过类型签名搜索 Java API 方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆