Java 中的屠龙之术:如何修改语法树?
来源:my.oschina.net/u/
4030990/blog/3211858
在网上关于如何修改Java的抽象语法树的相关API文档并不多,于是本篇记录一下相关的知识点,以便随后查阅。
JCTree的介绍
JCTree是语法树元素的基类,包含一个重要的字段pos,该字段用于指明当前语法树节点(JCTree)在语法树中的位置,因此我们不能直接用new关键字来创建语法树节点,即使创建了也没有意义。
此外,结合访问者模式,将数据结构与数据的处理进行解耦,部分源码如下:
public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
public int pos = -1;
...
public abstract void accept(JCTree.Visitor visitor);
...
}
我们可以看到JCTree是一个抽象类,这里重点介绍几个JCTree的子类
JCStatement:声明 语法树节点,常见的子类如下
JCBlock:语句块 语法树节点
JCReturn:return语句 语法树节点 JCClassDecl:类定义 语法树节点 JCVariableDecl:字段/变量定义 语法树节点
JCMethodDecl:方法定义 语法树节点
JCModifiers:访问标志 语法树节点
JCExpression:表达式 语法树节点,常见的子类如下
JCAssign:赋值语句 语法树节点
JCIdent:标识符 语法树节点,可以是变量,类型,关键字等等
推荐下自己做的 Spring Boot 的实战项目:
https://github.com/YunaiV/ruoyi-vue-pro
TreeMaker介绍
TreeMaker用于创建一系列的语法树节点,我们上面说了创建JCTree不能直接使用new关键字来创建,所以Java为我们提供了一个工具,就是TreeMaker,它会在创建时为我们创建的JCTree对象设置pos字段,所以必须使用上下文相关的TreeMaker对象来创建语法树节点。
具体的API介绍可以参照,TreeMakerAPI,接下来着重介绍一下常用的几个方法。
TreeMaker.Modifiers
TreeMaker.Modifiers
方法用于创建访问标志 语法树节点(JCModifiers),源码如下
public JCModifiers Modifiers(long flags) {
return Modifiers(flags, List.< JCAnnotation >nil());
}
public JCModifiers Modifiers(long flags,
List annotations) {
JCModifiers tree = new JCModifiers(flags, annotations);
boolean noFlags = (flags & (Flags.ModifierFlags | Flags.ANNOTATION)) == 0;
tree.pos = (noFlags && annotations.isEmpty()) ? Position.NOPOS : pos;
return tree;
}
flags:访问标志 annotations:注解列表
其中flags可以使用枚举类com.sun.tools.javac.code.Flags
来表示,例如我们可以这样用,就生成了下面的访问标志了。
treeMaker.Modifiers(Flags.PUBLIC + Flags.STATIC + Flags.FINAL);
public static final
TreeMaker.ClassDef
TreeMaker.ClassDef用于创建类定义 语法树节点(JCClassDecl),源码如下:
public JCClassDecl ClassDef(JCModifiers mods,
Name name,
List typarams,
JCExpression extending,
List implementing,
List defs) {
JCClassDecl tree = new JCClassDecl(mods,
name,
typarams,
extending,
implementing,
defs,
null);
tree.pos = pos;
return tree;
}
mods:访问标志,可以通过 TreeMaker.Modifiers
来创建name:类名 typarams:泛型参数列表 extending:父类 implementing:实现的接口 defs:类定义的详细语句,包括字段、方法的定义等等
TreeMaker.MethodDef
TreeMaker.MethodDef用于创建方法定义 语法树节点(JCMethodDecl),源码如下
public JCMethodDecl MethodDef(JCModifiers mods,
Name name,
JCExpression restype,
List typarams,
List params,
List thrown,
JCBlock body,
JCExpression defaultValue) {
JCMethodDecl tree = new JCMethodDecl(mods,
name,
restype,
typarams,
params,
thrown,
body,
defaultValue,
null);
tree.pos = pos;
return tree;
}
public JCMethodDecl MethodDef(MethodSymbol m,
Type mtype,
JCBlock body) {
return (JCMethodDecl)
new JCMethodDecl(
Modifiers(m.flags(), Annotations(m.getAnnotationMirrors())),
m.name,
Type(mtype.getReturnType()),
TypeParams(mtype.getTypeArguments()),
Params(mtype.getParameterTypes(), m),
Types(mtype.getThrownTypes()),
body,
null,
m).setPos(pos).setType(mtype);
}
mods:访问标志 name:方法名 restype:返回类型 typarams:泛型参数列表 params:参数列表 thrown:异常声明列表 body:方法体 defaultValue:默认方法(可能是interface中的哪个default) m:方法符号 mtype:方法类型。包含多种类型,泛型参数类型、方法参数类型、异常参数类型、返回参数类型。
返回类型restype填写null或者
treeMaker.TypeIdent(TypeTag.VOID)
都代表返回void类型。
TreeMaker.VarDef
TreeMaker.VarDef用于创建字段/变量定义 语法树节点(JCVariableDecl),源码如下
public JCVariableDecl VarDef(JCModifiers mods,
Name name,
JCExpression vartype,
JCExpression init) {
JCVariableDecl tree = new JCVariableDecl(mods, name, vartype, init, null);
tree.pos = pos;
return tree;
}
public JCVariableDecl VarDef(VarSymbol v,
JCExpression init) {
return (JCVariableDecl)
new JCVariableDecl(
Modifiers(v.flags(), Annotations(v.getAnnotationMirrors())),
v.name,
Type(v.type),
init,
v).setPos(pos).setType(v.type);
}
mods:访问标志 name:参数名称 vartype:类型 init:初始化语句 v:变量符号
TreeMaker.Ident
TreeMaker.Ident用于创建标识符 语法树节点(JCIdent),源码如下
public JCIdent Ident(Name name) {
JCIdent tree = new JCIdent(name, null);
tree.pos = pos;
return tree;
}
public JCIdent Ident(Symbol sym) {
return (JCIdent)new JCIdent((sym.name != names.empty)
? sym.name
: sym.flatName(), sym)
.setPos(pos)
.setType(sym.type);
}
public JCExpression Ident(JCVariableDecl param) {
return Ident(param.sym);
}
TreeMaker.Return
TreeMaker.Return用于创建return语句 (JCReturn),源码如下
public JCReturn Return(JCExpression expr) {
JCReturn tree = new JCReturn(expr);
tree.pos = pos;
return tree;
}
TreeMaker.Select
TreeMaker.Select用于创建域访问/方法访问 (这里的方法访问只是取到名字,方法的调用需要用TreeMaker.Apply)语法树节点(JCFieldAccess),源码如下
public JCFieldAccess Select(JCExpression selected,
Name selector)
{
JCFieldAccess tree = new JCFieldAccess(selected, selector, null);
tree.pos = pos;
return tree;
}
public JCExpression Select(JCExpression base,
Symbol sym) {
return new JCFieldAccess(base, sym.name, sym).setPos(pos).setType(sym.type);
}
selected: .
运算符左边的表达式selector: .
运算符右边的表达式
下面给出一个例子,一语句生成的Java语句就是二语句
一. TreeMaker.Select(treeMaker.Ident(names.fromString("this")), names.fromString("name"));
二. this.name
TreeMaker.NewClass
TreeMaker.NewClass用于创建new语句 语法树节点(JCNewClass),源码如下:
public JCNewClass NewClass(JCExpression encl,
List typeargs,
JCExpression clazz,
List args,
JCClassDecl def) {
JCNewClass tree = new JCNewClass(encl, typeargs, clazz, args, def);
tree.pos = pos;
return tree;
}
encl:不太明白此参数的含义,我看很多例子中此参数都设置为null typeargs:参数类型列表 clazz:待创建对象的类型 args:参数列表 def:类定义
TreeMaker.Apply
TreeMaker.Apply用于创建方法调用 语法树节点(JCMethodInvocation),源码如下:
public JCMethodInvocation Apply(List typeargs,
JCExpression fn,
List args) {
JCMethodInvocation tree = new JCMethodInvocation(typeargs, fn, args);
tree.pos = pos;
return tree;
}
typeargs:参数类型列表 fn:调用语句 args:参数列表
TreeMaker.Assign
TreeMaker.Assign用户创建赋值语句 语法树节点(JCAssign),源码如下:
ublic JCAssign Assign(JCExpression lhs,
JCExpression rhs) {
JCAssign tree = new JCAssign(lhs, rhs);
tree.pos = pos;
return tree;
}
lhs:赋值语句左边表达式 rhs:赋值语句右边表达式
TreeMaker.Exec
TreeMaker.Exec用于创建可执行语句 语法树节点(JCExpressionStatement),源码如下:
public JCExpressionStatement Exec(JCExpression expr) {
JCExpressionStatement tree = new JCExpressionStatement(expr);
tree.pos = pos;
return tree;
}
TreeMaker.Apply以及TreeMaker.Assign就需要外面包一层TreeMaker.Exec来获得一个JCExpressionStatement。
TreeMaker.Block
TreeMaker.Block用于创建组合语句 的语法树节点(JCBlock),源码如下:
public JCBlock Block(long flags,
List stats) {
JCBlock tree = new JCBlock(flags, stats);
tree.pos = pos;
return tree;
}
flags:访问标志 stats:语句列表
推荐下自己做的 Spring Cloud 的实战项目:
https://github.com/YunaiV/onemall
com.sun.tools.javac.util.List介绍
在我们操作抽象语法树的时候,有时会涉及到关于List的操作,但是这个List不是我们经常使用的java.util.List
而是com.sun.tools.javac.util.List
,这个List比较奇怪,是一个链式的结构,有头结点和尾节点,但是只有尾节点是一个List,这里作为了解就行了。
public class List<A> extends AbstractCollection<A> implements java.util.List<A> {
public A head;
public List tail;
private static final List EMPTY_LIST = new List