56

I wish to parse java source code files, and extract the methods source code.

I would need a method like this :

/** Returns a map with key = method name ; value = method source code */
Map<String,String> getMethods(File javaFile);

Is there a simple way to achieve this, a library to help me build my method, etc. ?

glmxndr
  • 45,516
  • 29
  • 93
  • 118

2 Answers2

63

Download the java parser from https://javaparser.org/

You'll have to write some code. This code will invoke the parser... it will return you a CompilationUnit:

            InputStream in = null;
            CompilationUnit cu = null;
            try
            {
                    in = new SEDInputStream(filename);
                    cu = JavaParser.parse(in);
            }
            catch(ParseException x)
            {
                 // handle parse exceptions here.
            }
            finally
            {
                  in.close();
            }
            return cu;

Note: SEDInputStream is a subclass of input stream. You can use a FileInputStream if you want.


You'll have to create a visitor. Your visitor will be easy because you're only interested in methods:

  public class MethodVisitor extends VoidVisitorAdapter
  {
        public void visit(MethodDeclaration n, Object arg)
        {
             // extract method information here.
             // put in to hashmap
        }
  }

To invoke the visitor, do this:

  MethodVisitor visitor = new MethodVisitor();
  visitor.visit(cu, null);
koppor
  • 19,079
  • 15
  • 119
  • 161
arcticfox
  • 878
  • 7
  • 8
  • 3
    Great answer. Appreciate the effort. Thanks. – glmxndr Feb 05 '10 at 10:49
  • 4
    Great answer is great. Thanks, it helps people even today ;) – dantuch Jul 13 '12 at 20:03
  • 2
    The project is no longer maintained. Check http://code.google.com/p/javaparser/issues/detail?id=9#c32 which leads you to https://github.com/matozoid/javaparser – jedierikb Jan 06 '13 at 17:33
  • 4
    The project is maintained at https://github.com/javaparser/javaparser and we released version 2.1 (fully supporting Java 8) a few weeks ago. Enjoy! – Federico Tomassetti Jun 08 '15 at 20:31
  • @FedericoTomassetti Did you implement everything from scratch? I don't know much about Java but doesn't the java compiler have the parser classes? – Kenji Noguchi Dec 12 '16 at 04:45
  • I am a contributor, I did not write JavaParser from scratch. No, there is not such functionality as part of the JDK – Federico Tomassetti Dec 12 '16 at 07:53
  • @FedericoTomassetti This is not true, http://docs.oracle.com/javase/8/docs/api/javax/tools/package-summary.html contains a blazing fast parser as part of the compilation pipeline. – Lee Jan 11 '17 at 04:14
  • 1
    @Lee, yes I know it has a compiler, but this is different from a parser. A compiler needs to resolve symbols, so you need to have the dependencies to use that. There is code that can be parsed but cannot be compiled (because of semantic errors). So they are not the same thing. The parser is the first step of a compiler. JavaParser permits to build an AST, modify it and write back the code. I am not aware you can do that with the compiler API. Do you? – Federico Tomassetti Jan 11 '17 at 08:37
  • 4
    The Javac compiler API has a fully accessible parsing API within the JDK. It's a bit convoluted, but you can get the system compiler (`ToolProvider.getSystemJavaCompiler()`), get its `JavacTask` via `compiler.getTask(...)`, and have it parse via `task.parse()`, which returns a collection of `CompilationUnitTree`s. The Sun/Oracle parser is actually much faster than even the ECJ parser, though it does not have the same level of error-inference that ECJ is capable of (for example, ECJ can give suggestions on what you mean, or partially parse code that is "mostly" correct). – Lee Jan 11 '17 at 23:42
  • @FedericoTomassetti "No, there is not such functionality as part of the JDK" was wrong in 2016. The Java Compiler API has been part of Java since the version 1.6 released in December 2006. – gouessej Sep 13 '22 at 22:17
1

I implemented lee's suggestion, there is no need of third party libraries to achieve that, the following example prints the names of the methods (tested with Java 17 but should work with Java 1.6 with minor changes):

import com.sun.source.util.JavacTask;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

public class Main {

    public static void main(final String[] args) throws Exception {
        final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        try (final StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, StandardCharsets.UTF_8)) {
            final Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(Arrays.asList(new File(args[0])));
            final JavacTask javacTask = (JavacTask) compiler.getTask(null, fileManager, null, null, null, compilationUnits);
            final Iterable<? extends CompilationUnitTree> compilationUnitTrees = javacTask.parse();
            final ClassTree classTree = (ClassTree) compilationUnitTrees.iterator().next().getTypeDecls().get(0);
            final List<? extends Tree> classMemberList = classTree.getMembers();
            final List<MethodTree> classMethodMemberList = classMemberList.stream()
                .filter(MethodTree.class::isInstance)
                .map(MethodTree.class::cast)
                .collect(Collectors.toList());
            // just prints the names of the methods
            classMethodMemberList.stream().map(MethodTree::getName)
                .forEachOrdered(System.out::println);
        }
    }

}

Note that other solutions except ANTLR don't support very latest versions of Java, javaparser doesn't fully support 19 currently (January 2023), JavaCC doesn't seem to support Java >= 9 according to its public documentation.

Federico Tomassetti wrote in 2016 that there was no parsing functionality as part of the JDK, I replied that he was wrong. I have nothing against third party libraries but providing false information to developers in order to promote her/his stuff is not honest and is not the kind of behavior I expect on StackOverflow. I use some classes and APIs available in Java since Java 1.6 released in December 2006.

gouessej
  • 3,640
  • 3
  • 33
  • 67
  • Very nice ! I tested your code and it works perfectly ! could it be possible to say how to do the same to get class fields ? – froggy Apr 11 '23 at 12:21
  • 1
    Thank you for the compliment by the way, I got some downvotes for this detailed answer. Yes, you can replace `MethodTree` in the filter by `VariableTree`. Actually, I advise you to use the debug mode to look at the content of the variable `classMemberList` because there are many possibilities among those subinterfaces: https://docs.oracle.com/en/java/javase/20/docs/api/jdk.compiler/com/sun/source/tree/Tree.html VariableTree doesn't only represent field declarations, it's used for local variable declarations too. – gouessej Apr 11 '23 at 21:59
  • VariableTree ! Nice the class i was looking for ! I searched for fields and properties ... haven't thought about variable. Yes i saw that, seems Stack became since 10 years more and more unfriendly and form oriented than content... While I am on it, is possible to also obtain parent & implemented classes ? – froggy Apr 12 '23 at 09:09
  • 1
    Yes, getExtendsClause() returns the single tree of the parent and getImplementsClause() returns a list of trees, each tree represents an implemented interface. – gouessej Apr 12 '23 at 13:32