javaabstract-syntax-treeeclipse-jdtjavaparser

How to extract metadata from Java methods using AST?


Is there an AST tool that allows easily extract metadata from a Java method?

For instance, using the following code snippet

/*
 Checks if a target integer is present in the list of integers.
*/
public Boolean contains(Integer target, List<Integer> numbers) {
    for(Integer number: numbers){
        if(number.equals(target)){
            return true;
        }
    }
    return false;
}

the metadata would be:

metadata = {
    "comment": "Checks if a target integer is present in the list of integers.",
    "identifier": "contains",
    "parameters": "Integer target, List<Integer> numbers",
    "return_statement": "Boolean false"

}

Solution

  • This class was written a long time ago.. It was actually about four different classes - spread out in a package called JavaParserBridge. It tremendously simplifies what you are trying to do. I have stripped out all the unneccessary stuff, and boiled it down to 100 lines. It took about an hour...

    I hope this all makes sense. I usually add a lot of comments to code, but sometimes when dealing with other libraries - and posting on Stack Overflow - since this is literally just one big constructor - I will leave you with the documentation page for Java Parser

    To use this class, just pass the source-code file for a Java Class as a single java.lang.String, and the method named getMethods(String) will return a Java Vector<Method>. Each element of the returned Vector will have an instance of Method which shall have all of the Meta Information that you requested in your question.

    IMPORTANT: You can get the JAR File for this package off of the github page. You need the JAR named: javaparser-core-3.16.2.jar

    import com.github.javaparser.StaticJavaParser;
    import com.github.javaparser.ast.CompilationUnit;
    import com.github.javaparser.ast.body.TypeDeclaration;
    import com.github.javaparser.ast.body.MethodDeclaration;
    import com.github.javaparser.ast.body.Parameter;
    import com.github.javaparser.ast.type.ReferenceType;
    import com.github.javaparser.ast.type.TypeParameter;
    import com.github.javaparser.ast.Node;
    import com.github.javaparser.ast.NodeList;
    import com.github.javaparser.ast.Modifier; // Modifiers are the key-words such as "public, private, static, etc..."
    import com.github.javaparser.printer.lexicalpreservation.LexicalPreservingPrinter;
    import com.github.javaparser.printer.lexicalpreservation.PhantomNodeLogic;
    
    import java.io.IOException;
    import java.util.Vector;
    
    
    public class Method
    {
        public final String name, signature, jdComment, body, returnType;
        public final String[] modifiers, parameterNames, parameterTypes, exceptions;
    
        private Method (MethodDeclaration md)
        {
    
            NodeList<Parameter>     paramList       = md.getParameters();
            NodeList<ReferenceType> exceptionList   = md.getThrownExceptions();
            NodeList<Modifier>      modifiersList   = md.getModifiers();
    
            this.name           = md.getNameAsString();
            this.signature      = md.getDeclarationAsString();
            this.jdComment      = (md.hasJavaDocComment() ? md.getJavadocComment().get().toString() : null);
            this.returnType     = md.getType().toString();
            this.modifiers      = new String[modifiersList.size()];
            this.parameterNames = new String[paramList.size()];
            this.parameterTypes = new String[paramList.size()];
            this.exceptions     = new String[exceptionList.size()];
            this.body           = (md.getBody().isPresent()
                                    ?   LexicalPreservingPrinter.print
                                            (LexicalPreservingPrinter.setup(md.getBody().get()))
                                    :   null);
    
            int i=0;
            for (Modifier modifier : modifiersList) modifiers[i++] = modifier.toString();
    
            i=0;
            for (Parameter p : paramList)
            {
                parameterNames[i]           = p.getName().toString();
                parameterTypes[i]           = p.getType().toString();
                i++;
            }
    
            i=0;
            for (ReferenceType r : exceptionList) this.exceptions[i++] = r.toString();
        }
    
        public static Vector<Method> getMethods(String sourceFileAsString) throws IOException
        {
            // This is the "Return Value" for this method (a Vector)
            final Vector<Method> methods = new Vector<>();
    
            // This asks Java Parser to parse the source code file
            // The String-parameter 'sourceFileAsString' should have this
    
            CompilationUnit cu = StaticJavaParser.parse(sourceFileAsString);
    
            // This will "walk" all of the methods that were parsed by
            // StaticJavaParser, and retrieve the method information.
            // The method information is stored in a class simply called "Method"
    
            cu.walk(MethodDeclaration.class, (MethodDeclaration md) -> methods.add(new Method(md)));
    
            // There is one important thing to do: clear the cache
            // Memory leaks shall occur if you do not.
    
            PhantomNodeLogic.cleanUpCache(); 
    
            // return the Vector<Method>
            return methods;
        }
    }