一、JDT AST介绍
1.概念
抽象语法树(Abstract Syntax Tree, AST)使用树形结构来表示源代码的抽象语法结构,树上的每一个节点都对应源代码中的一种结构。
2.举例感受抽象语法树
①运算表达式
表达式:1+2*(3-4)+5
抽象语法树:
②代码语句块
抽象语法树:
③Java文件
3.AST中3个关键类
①org.eclipse.jdt.core.dom.AST
作用:AST的工厂类,用于创建表示各种语法结构的节点。一般用于创建AST。
②org.eclipse.jdt.core.dom.ASTNode及其子类(AST节点类)
作用:用于表示Java中的所有语法结构。每个具体的子类,都代表着一种Java语法结构。某一个节点源代码具体到底是什么类型的节点,可以通过ASTView来查看。
- 安装eclipse astview插件。
- 分析java demo代码,观察语法树的各个节点和层级关系。
- ASTNode的主要子类:
Expression系列如下: - 编译单元作为根节点的一般树状节点模型(叶子节点基本为名称、操作符和字面量):
③org.eclipse.jdt.core.dom.ASTVisitor(ASTVisitor类)
如何获取具体ASTNode节点中的数据呢?
- 使用具体ASTNode的特性属性、特性方法获取。
- ASTVisitor:AST访问者类获取。介绍如下:
它针对不同类型的节点提供了一系列重载的visit()和endvisit()方法,意思就是访问到该类型的节点时执行visit()方法,访问完该类型节点后执行endvisit()方法。其中,visit()方法需返回boolean类型的返回值,表示是否继续访问子节点。
这些方法的具体实现由ASTVisitor的子类负责,如果不需要对所访问到的节点做处理,则无需在ASTVisitor的子类中覆盖这些方法。
Eclipse AST访问节点这一部分的设计采用了访问者模式,不同类型的节点是待访问的具体元素,ASTNode充当抽象元素角色,ASTVisitor充当抽象访问者,而我们自己写的ASTVisitor的子类充当具体访问者,而程序代码就是对象结构,包含了不同种类的节点供访问者来访问。
在使用 Eclipse AST访问节点时需要先声明一个类继承自ASTVisitor,即增加具体访问者类,并覆盖相应的方法,编写需执行的操作,实例化这个访问者类后调用ASTNode的accept()方法,将ASTVisitor作为参数传入,就可以执行访问了,此处对应了访问者模式的“双重分派”机制。
测试代码:
public class Demo1 {
/*
* 字段1、字段2
*/
private String field1 = "Hello World", field2;
/**
*
* @param d1 被除数
* @param d2 除数
* @return
*/
@RequestMapping("/divide")
public Double divide(Double d1, Double d2) {
Double result = null;
if (d2 == null) {
result = (double) 0;
} else {
result = d1 / d2;
for (int i = 0; i < 10; i++) {
result /= d2;
}
}
return result;
}
// 加法
public void plus(int a, int b) {
System.out.println(a + b);
}
/*
* 字段3
*/
private String field3;
}
ASTVisitor子类:
public class DeclarationSeriesVisitor extends ASTVisitor {
@Override
public boolean visit(FieldDeclaration node) {
List fragments = node.fragments();
for (Object obj : fragments) {
VariableDeclarationFragment v = (VariableDeclarationFragment) obj;
System.out.println("Field:" + v.getName());
}
return true;
}
@Override
public boolean visit(MethodDeclaration node) {
System.out.println("Method:" + node.getName());
return true;
}
@Override
public boolean visit(TypeDeclaration node) {
System.out.println("Class:" + node.getName());
return true;
}
}
客户端类:
public class App {
public static void main(String[] args) {
String javaFilePath = "F:\\workspace\\codeview-demo\\src\\main\\java\\com\\heshj\\codeview_demo\\Demo1.java";
CompilationUnit compilationUnit = JdtAstUtil.getCompilationUnit(javaFilePath);
DeclarationSeriesVisitor declarationSeriesVisitor = new DeclarationSeriesVisitor();
compilationUnit.accept(declarationSeriesVisitor);
}
}
debug跟一遍代码,感受一下这个具体代码树。
4.使用AST解析代码视图,需要注意的问题
①个人理解
ast使用树状结构存储,采用组合模式分析。我们试想,如果组合模式中的各个节点,都拥有相同的接口操作的话,操作将变得简单,在进行树遍历的时候,甚至可以不用在意具体类的实现。但是分析语法树是一件很严谨的事,由于Java语法很多,因此各个具体节点类都存在或多或少的差异。ast牺牲了通用性,采用各个具体子类具体分析,从而能够看出分析的复杂性。
优点:
1.ast采用ASTVisitor访问者模式来获取想要的任意节点信息;
2.其内部各个具体子节点类,已经封装好遍历下属节点的操作。我们无需自己去遍历树节点,自己遍历也遍历不完。
3 建议获取数据都采用访问者的方式获取,充分利用ast各个节点封装的解析。
缺点:
1.跨文件:ast只能分析彼此独立的java文件(编译单元),以编译单元为根节点的树形节点之间的关联,ast没有提供支持。举个例子,A文件中调用B文件的某个方法,那么在解析A文件这个编译单元时,访问到该调用方法表达式节点的时候,无法指向B文件对应的哪个申明方法节点。因此跨文件调用方法、获取属性字段等,将受到限制。
2.运行时:继承、接口实现、抽象的方法调用、属性继承等操作,无法找到对应的具体类。因为ast是静态代码扫描,无法获取到运行时的具体类型,这是一个局限。
针对上述两个不足,我们应该如何处理呢?