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:
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.
Integer.class
= Integer.TYPE
)findMethod
methodint[] -> 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;
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With