翻译自:www.baeldung.com/javaparser
1、javaParser 是什么
JavaParser它允许我们将Java源代码解析为抽象语法树(AST)。完成这些之后,我们就可以分析解析后的代码,对其进行操作,甚至编写新代码并输出。
GitHub地址:github.com/javaparser/…
2、maven依赖
<dependency>
<groupId>com.github.javaparser</groupId>
<artifactId>javaparser-core</artifactId>
<version>3.25.10</version>
</dependency>
3、解析源文件
3.1、解析源代码
CompilationUnit parsed = StaticJavaParser.parse("class TestClass {}");
3.2、解析语句
Statement parsed = StaticJavaParser.parseStatement("final int answer = 42;");
3.3、解析其他结构
JavaParser还可以解析许多其他结构,覆盖到Java 18之前的整个Java语言。每个构造都有一个单独的专用解析方法,并返回表示已解析代码的适当类型。例如,我们可以使用parseAnnotation()来解析注释,parseImport()来解析导入语句,parseBlock()来解析语句块,等等。为了选择正确的解析方法,我们确实需要确切地知道要解析的代码类型。例如,使用parseStatement()方法解析类定义将会失败。
4、分析解析代码
4.1、访问已解析元素
一旦我们解析了一些源代码,我们就可以查询AST来访问各个元素。具体如何做取决于我们想要访问的元素和我们解析的内容。
例如,如果我们已经将源文件解析为CompilationUnit,那么我们可以使用getClassByName()访问我们期望出现的类:
Optional<ClassOrInterfaceDeclaration> cls = compilationUnit.getClassByName("TestClass");
在每个阶段,我们只能直接访问我们当前处理的最外层的元素。例如,如果我们从解析源文件中得到一个CompilationUnit,那么我们可以访问包声明、导入语句和顶级类型,但是我们不能访问这些类型中的成员。但是,一旦访问了其中一个类型,就可以访问其中的成员。
4.2、 迭代解析元素
在某些情况下,我们可能不知道解析代码中出现了哪些元素,或者我们只是想处理所有特定类型的元素,而不仅仅是一个元素。
我们的每个AST类型都可以访问整个合适的嵌套元素范围。具体如何运作取决于我们想要处理什么。例如,我们可以使用以下命令从CompilationUnit中提取所有的import语句:
NodeList<ImportDeclaration> imports = compilationUnit.getImports();
4.3、迭代整个AST
除了从解析的代码中精确提取一种类型的元素外,我们还可以遍历整个解析树。JavaParser中的所有AST类型都实现了访问者模式,允许我们使用自定义访问者访问解析过的源代码中的每个元素:
compilationUnit.accept(visitor, arg);
我们可以使用两种标准类型的访问者。对于每种可能的AST类型,这两种方法都有一个visit()方法,该方法接受传递给accept()调用的状态参数。
其中最简单的是VoidVisitor<A> 。每个AST类型都有一个方法,没有返回值。然后,我们有了一个适配器类型——VoidVisitorAdapter——它为我们提供了一个标准实现,以帮助确保正确调用整个树。
然后我们只需要实现我们感兴趣的方法,例如:
compilationUnit.accept(new VoidVisitorAdapter<Object>() {
@Override
public void visit(MethodDeclaration n, Object arg) {
super.visit(n, arg);
System.out.println("Method: " + n.getName());
}
}, null);
这将为源文件中的每个方法名输出一条日志消息,而不管它们在哪里。这在整个树结构上递归的事实意味着这些方法可以位于顶级类、内部类,甚至是其他方法中的匿名类中。
另一种选择是GenericVisitor<R, A> 。 它的工作原理类似于VoidVisitor,只不过它的visit()方法有一个返回值。这里我们还有适配器类,这取决于我们希望如何从每个方法收集返回值。例如,GenericListVisitorAdaptor将强制我们让每个方法的返回类型为List而是将所有这些列表合并在一起:
List<String> allMethods = compilationUnit.accept(new GenericListVisitorAdapter<String, Object>() {
@Override
public List<String> visit(MethodDeclaration n, Object arg) {
List<String> result = super.visit(n, arg);
result.add(n.getName().asString());
return result;
}
}, null);
这将返回一个列表,其中包含整个树中每个方法的名称。
5、输出解析代码
除了解析和分析代码之外,我们还可以再次将其作为字符串输出。这在很多情况下都很有用——例如,如果我们只想提取和输出代码的特定部分。
实现这一点的最简单方法是使用标准的toString()方法。我们所有的AST类型都正确地实现了这一点,并将生成格式化的代码。注意,它的格式可能与我们解析代码时的格式不完全一样,但它仍然遵循相对标准的约定。
例如,如果我们解析以下代码:
package com.baeldung.javaparser;
import java.util.List;
class TestClass {
private List<String> doSomething() {}
private class Inner {
private String other() {}
}
}
当我们格式化它时,我们将得到这样的输出:
package com.baeldung.javaparser;
import java.util.List;
class TestClass {
private List<String> doSomething() {
}
private class Inner {
private String other() {
}
}
}
我们可以用于格式化代码的另一种方法是使用DefaultPrettyPrinterVisitor。这是一个处理格式化的标准访问者类。这为我们配置输出格式的某些方面提供了优势。例如,如果我们想用两个空格而不是四个空格缩进,我们可以这样写:
DefaultPrinterConfiguration printerConfiguration = new DefaultPrinterConfiguration();
printerConfiguration.addOption(new DefaultConfigurationOption(DefaultPrinterConfiguration.ConfigOption.INDENTATION,
new Indentation(Indentation.IndentType.SPACES, 2)));
DefaultPrettyPrinterVisitor visitor = new DefaultPrettyPrinterVisitor(printerConfiguration);
compilationUnit.accept(visitor, null);
String formatted = visitor.toString();
6、操作已解析代码
一旦我们将一些代码解析为AST,我们还可以对其进行更改。解析后它只是一个Java对象模型,我们可以像对待任何其他对象模型一样对待它,并且JavaParser使我们能够自由地更改它的大多数方面。
将此功能与将AST作为工作源代码输出的能力相结合,意味着我们可以操作已解析的代码,对其进行更改,并以某种形式提供输出。这对于IDE插件、代码编译步骤以及更多内容可能很有用。
只要我们能够访问适当的AST元素,就可以以任何方式使用它——无论是直接访问它们,还是与访问者进行迭代,或者任何有意义的方式。
例如,如果我们想把代码中的每个方法名都大写,那么我们可以这样做:
compilationUnit.accept(new VoidVisitorAdapter<Object>() {
@Override
public void visit(MethodDeclaration n, Object arg) {
super.visit(n, arg);
String oldName = n.getName().asString();
n.setName(oldName.toUpperCase());
}
}, null);
它使用一个简单的访问器来访问源树中的每个方法声明,并使用setName()方法为每个方法赋予一个新名称。新名称就是旧名称的大写形式。
完成此操作后,AST将被就地更新。然后,我们可以按照自己的意愿对其进行格式化,新格式化的代码将反映我们的更改。