Java:解析Java源代码,提取方法

56

我希望解析Java源代码文件,并提取方法的源代码。

我需要一个像这样的方法:

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

有没有简单的方法来实现这个功能,一个可以帮助我构建我的方法的库等?


Roaster 提供了一个流畅的 API 来处理 Java 代码。 - koppor
@koppor它也能解析源代码中的catch块吗? - Gaurav
2个回答

63

https://javaparser.org/下载Java解析器。

您需要编写一些代码,该代码将调用解析器...它将返回一个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;

注意:SEDInputStream是输入流的一个子类。如果需要,可以使用FileInputStream。


您需要创建一个访问者。由于您只关心方法,因此您的访问者将很容易:

  public class MethodVisitor extends VoidVisitorAdapter
  {
        public void visit(MethodDeclaration n, Object arg)
        {
             // extract method information here.
             // put in to hashmap
        }
  }
调用访问者,只需执行以下操作:
  MethodVisitor visitor = new MethodVisitor();
  visitor.visit(cu, null);

3
好的回答。感谢努力。谢谢。 - glmxndr
4
好的,非常感谢您的夸奖和反馈。这个答案依然对人们有帮助,即使到今天为止。 - dantuch
2
该项目已不再维护。请查看http://code.google.com/p/javaparser/issues/detail?id=9#c32,该链接将引导您到https://github.com/matozoid/javaparser。 - jedierikb
4
该项目托管在https://github.com/javaparser/javaparser,我们几周前发布了2.1版本(完全支持Java 8)。祝使用愉快! - Federico Tomassetti
4
Javac编译器API在JDK内部具有完全可访问的解析API。虽然有些复杂,但您可以获取系统编译器(ToolProvider.getSystemJavaCompiler()),通过compiler.getTask(...)获取其JavacTask,并通过task.parse()进行解析,返回一个CompilationUnitTree集合。Sun/Oracle解析器实际上比ECJ解析器更快,尽管它没有ECJ能力那么高的错误推断水平(例如,ECJ可以提供您意图的建议,或解析“大多数”正确的代码)。 - Lee
显示剩余5条评论

1

我实现了lee的建议,无需第三方库即可实现,以下示例打印方法名称(在Java 17上测试过,但在Java 1.6上应该只需要进行微小的更改):

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

}

请注意,除了ANTLR之外的其他解决方案不支持最新版本的Java,javaparser目前(2023年1月)不完全支持19,根据其公共文档,JavaCC似乎不支持Java >= 9。 Federico Tomassetti在2016年写道JDK中没有解析功能,我回复他是错误的。我并不反对第三方库,但为了推广自己的东西而向开发人员提供虚假信息是不诚实的行为,也不是我在StackOverflow上期望看到的行为。我使用自Java 1.6于2006年12月发布以来可用的一些类和API。

非常好!我测试了你的代码,它完美地运行了!能否告诉我如何获取类字段? - froggy
1
顺便说一句,谢谢夸奖,对于这个详细的答案我得到了一些踩。是的,在过滤器中可以使用VariableTree替换MethodTree。实际上,我建议您使用调试模式查看变量classMemberList的内容,因为在那些子接口中有许多可能性:https://docs.oracle.com/en/java/javase/20/docs/api/jdk.compiler/com/sun/source/tree/Tree.html VariableTree不仅代表字段声明,还用于局部变量声明。 - gouessej
VariableTree!太棒了,这正是我在寻找的类!我一直在搜索字段和属性……没有考虑过变量。是的,我看到了,似乎Stack在过去十年中变得越来越不友好和形式导向,而非内容导向......顺便问一下,是否可能还可以获取父级和已实现的类? - froggy
1
是的,getExtendsClause() 返回父类的单棵树,而 getImplementsClause() 则返回一个树列表,其中每棵树代表一个已实现的接口。 - gouessej

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接