javamavencompiler-constructionabstract-syntax-treefully-qualified-naming

Getting qualified method name by the line number


This question is Java and Maven specific. Please note the additional constraints below as they are different from other questions.

I have several Maven (Java) projects to analyze. What I have is:

The question is: Given one source code file (.java) and a line number there, how can I get the fully qualified name of the method that spans over that line? If the line is not in a method then just output null. Acceptable languages to implement this are: Java, ruby, or python.

Could you please answer the question in one of the following two ways?

  1. use the binary and extract qualified method name of that line. (This might involve weave in debug info, but that is fine.)

  2. directly use the source file given, try to parse it and use the AST.

Using specific libraries (like BCEL) or any 3rd party ones (as long as they are well documented and usable) are OK, too.

Many many thanks for the huge help!


Solution

  • Unfortunately, your question is full of drawbacks:

    Augh! That leads me to a kind of complicated solution:

    I'll declare a class which will contain all the login:

    public class SourceMethodsIndexer
    {
        private final SortedMap<Integer, List<Method>> indexOfMethodsByFirstLineNumber;
    }
    

    The constructor will be like this:

    public SourceMethodsIndexer(File sourceFile)
    

    ... and should do these tasks:

    1.Browse the class directory related to the target package.

    File targetPackageDir=getTargetPackageDir(sourceFile);
    File[] classFiles=targetPackageDir.listFiles(new FileFilter(){
        public boolean accept(File dir, String name){
            return name.endsWith(".class");
    }
    

    });

    2.Use Apache BCEL to collect all the non public classes belonging to your input source file (you can invoke JavaClass.getSourceFileName() to filter classes), plus the public class corresponding to the name of your input source file.

    Collection<JavaClass> targetClasses=getNonPublicClasses(classFiles, sourceFile.getName());
    targetClasses.add(publicClass);
    

    3.Collect then all the methods in each class.

    Set<Method> targetMethods=new HashSet<Method>(1024);
    for (JavaClass javaClass:targetClasses)
    {
        targetMethods.addAll(Arrays.asList(javaClass.getMethods()));
    }
    

    4.Now you can either search directly your line number, or index first the methods by line number to access them later more quickly: JavaClass.getMethods()[n].getLineNumberTable().getSourceLine(0) (take care that there could be repeated values).

    this.indexOfMethodsByFirstLineNumber=new TreeMap<Integer, List<Method>>((int)(1.7d*methods.size()));
    for (Method method: methods)
    {
        // Note: The -1 in this line stands to make the SortedMap work properly when searching for ranges.
        int firstLine=getLineNumberTable().getSourceLine(0)-1;
        List<Method> methodsInTheSameLine=indexOfMethodsByFirstLineNumber.get(firstLine);
        if (methodsInTheSameLine==null)
        {
            methodsInTheSameLine=new ArrayList<Method>();
            indexOfMethodsByFirstLineNumber.put(firstLine,methodsInTheSameLine);
        }
        methodsInTheSameLine.add(method);
    }
    

    5.Public a method to do the search:

    public Method getMethodByLine(int lineNumber)
    {
        Set<Method> methodsInTheSameLine=this.indexOfMethodsByFirstLineNumber.headMap(lineNumber).lastKey();
        if (methodsInTheSameLine.size()==0)
        {
            // There are no methods method in that line: Absurd.
        }
        else if (methodsInTheSameLine.size()>1)
        {
            // There are more than one method in that line. Hardly probable, but possible.
        }
        else
        {
            // There is one method in that line:
            return methods.get(0);
        }
    }