开发者

How to search for Java API methods by type signature?

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

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

yielding

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

yielding

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

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

I'd expect the tool to:

  • ignore the order of parameters
  • include methods which accept 'wider' types as parameters (e.g. superclasses)
  • include methods which return 'narrower' types as return values (e.g. subclasses)
  • treat the 'self' value as a parameter to instance methods (e.g. 'String -> int' would include String#hashCode)

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.


Solution comments:

  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

This is the output of the program:

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)

The code:

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;
    }
}


I just tried this on Eclipse (Helios) and it supports this. Hit Ctrl-H, go to Java tab, choose Search For -> Method radio button, Limit To -> Declarations. In the Search string enter *(int, int) int and it will return lots of results of methods that take two ints and return an int.


Very good question although I do not understand why do you need such tool. I am sorry to say it but it seems that it takes less time to implement such tool than to write this post. Here is a code that I have just implemented. It took 182 seconds. It is a static method that takes class, return type and arguments and returns all method of the class that match the signature.

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()]);
    }
}

You can spend another 5-10 minutes to implement method that opens jar, iterates over entries, calls Class.forName() and then calls my method. That's it!

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜