共计 13363 个字符,预计需要花费 34 分钟才能阅读完成。
作者:不学有数的程序员
起源:https://my.oschina.net/u/4030…
在网上对于如何批改 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:标识符 语法树节点,能够是变量,类型,关键字等等
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<JCAnnotation> 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<JCTypeParameter> typarams,
JCExpression extending,
List<JCExpression> implementing,
List<JCTree> 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<JCTypeParameter> typarams,
List<JCVariableDecl> params,
List<JCExpression> 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<JCExpression> typeargs,
JCExpression clazz,
List<JCExpression> 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<JCExpression> typeargs,
JCExpression fn,
List<JCExpression> 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<JCStatement> stats) {JCBlock tree = new JCBlock(flags, stats);
tree.pos = pos;
return tree;
}
- flags:拜访标记
- stats:语句列表
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<A> tail;
private static final List<?> EMPTY_LIST = new List<Object>((Object)null, (List)null) {public List<Object> setTail(List<Object> var1) {throw new UnsupportedOperationException();
}
public boolean isEmpty() {return true;}
};
List(A head, List<A> tail) {
this.tail = tail;
this.head = head;
}
public static <A> List<A> nil() {return EMPTY_LIST;}
public List<A> prepend(A var1) {return new List(var1, this);
}
public List<A> append(A var1) {return of(var1).prependList(this);
}
public static <A> List<A> of(A var0) {return new List(var0, nil());
}
public static <A> List<A> of(A var0, A var1) {return new List(var0, of(var1));
}
public static <A> List<A> of(A var0, A var1, A var2) {return new List(var0, of(var1, var2));
}
public static <A> List<A> of(A var0, A var1, A var2, A... var3) {return new List(var0, new List(var1, new List(var2, from(var3))));
}
...
}
com.sun.tools.javac.util.ListBuffer
因为 com.sun.tools.javac.util.List
应用起来不不便,所以又在其下面封装了一层,这个封装类是 ListBuffer
,此类的操作和咱们平时常常应用的java.util.List
用法十分相似。
public class ListBuffer<A> extends AbstractQueue<A> {public static <T> ListBuffer<T> of(T x) {ListBuffer<T> lb = new ListBuffer<T>();
lb.add(x);
return lb;
}
/** The list of elements of this buffer.
*/
private List<A> elems;
/** A pointer pointing to the last element of 'elems' containing data,
* or null if the list is empty.
*/
private List<A> last;
/** The number of element in this buffer.
*/
private int count;
/** Has a list been created from this buffer yet?
*/
private boolean shared;
/** Create a new initially empty list buffer.
*/
public ListBuffer() {clear();
}
/** Append an element to buffer.
*/
public ListBuffer<A> append(A x) {x.getClass(); // null check
if (shared) copy();
List<A> newLast = List.<A>of(x);
if (last != null) {
last.tail = newLast;
last = newLast;
} else {elems = last = newLast;}
count++;
return this;
}
........
}
com.sun.tools.javac.util.Names 介绍
这个是为咱们创立名称的一个工具类,无论是类、办法、参数的名称都须要通过此类来创立。它外面常常被应用到的一个办法就是fromString()
,个别应用办法如下所示。
Names names = new Names()
names. fromString("setName");
实战演练
下面咱们大略理解了如何操作形象语法树,接下来咱们就来写几个实在的案例加深了解。
变量相干
在类中咱们常常操作的参数就是变量,那么如何应用形象语法树的个性为咱们操作变量呢?接下来咱们就将一些对于变量的一些操作。
生成变量
例如生成 private String age;
这样一个变量,借用咱们下面讲的 VarDef
办法
// 生成参数 例如:private String age;
treeMaker.VarDef(treeMaker.Modifiers(Flags.PRIVATE), names.fromString("age"), treeMaker.Ident(names.fromString("String")), null);
对变量赋值
例如咱们想生成 private String name = "BuXueWuShu"
,还是利用VarDef
办法
// private String name = "BuXueWuShu"
treeMaker.VarDef(treeMaker.Modifiers(Flags.PRIVATE),names.fromString("name"),treeMaker.Ident(names.fromString("String")),treeMaker.Literal("BuXueWuShu"))
两个字面量相加
例如咱们生成 String add = "a" + "b";
,借用咱们下面讲的Exec
办法和 Assign
办法
// add = "a"+"b"
treeMaker.Exec(treeMaker.Assign(treeMaker.Ident(names.fromString("add")),treeMaker.Binary(JCTree.Tag.PLUS,treeMaker.Literal("a"),treeMaker.Literal("b"))))
+= 语法
例如咱们想生成add += "test"
,则和下面字面量差不多。
// add+="test"
treeMaker.Exec(treeMaker.Assignop(JCTree.Tag.PLUS_ASG, treeMaker.Ident(names.fromString("add")), treeMaker.Literal("test")))
++ 语法
例如想生成++i
treeMaker.Exec(treeMaker.Unary(JCTree.Tag.PREINC,treeMaker.Ident(names.fromString("i"))))
办法相干
咱们对于变量进行了操作,那么基本上都是要生成办法的,那么如何对办法进行生成和操作呢?咱们接下来演示一下对于办法相干的操作方法。
无参无返回值
咱们能够利用下面讲到的 MethodDef
办法进行生成
/*
无参无返回值的办法生成
public void test(){}
*/
// 定义方法体
ListBuffer<JCTree.JCStatement> testStatement = new ListBuffer<>();
JCTree.JCBlock testBody = treeMaker.Block(0, testStatement.toList());
JCTree.JCMethodDecl test = treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), // 办法限定值
names.fromString("test"), // 办法名
treeMaker.Type(new Type.JCVoidType()), // 返回类型
com.sun.tools.javac.util.List.nil(),
com.sun.tools.javac.util.List.nil(),
com.sun.tools.javac.util.List.nil(),
testBody, // 办法体
null
);
有参无返回值
咱们能够利用下面讲到的 MethodDef
办法进行生成
/*
无参无返回值的办法生成
public void test2(String name){name = "xxxx";}
*/
ListBuffer<JCTree.JCStatement> testStatement2 = new ListBuffer<>();
testStatement2.append(treeMaker.Exec(treeMaker.Assign(treeMaker.Ident(names.fromString("name")),treeMaker.Literal("xxxx"))));
JCTree.JCBlock testBody2 = treeMaker.Block(0, testStatement2.toList());
// 生成入参
JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER), names.fromString("name"),treeMaker.Ident(names.fromString("String")), null);
com.sun.tools.javac.util.List<JCTree.JCVariableDecl> parameters = com.sun.tools.javac.util.List.of(param);
JCTree.JCMethodDecl test2 = treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), // 办法限定值
names.fromString("test2"), // 办法名
treeMaker.Type(new Type.JCVoidType()), // 返回类型
com.sun.tools.javac.util.List.nil(),
parameters, // 入参
com.sun.tools.javac.util.List.nil(),
testBody2,
null
);
有参有返回值
/*
有参有返回值
public String test3(String name){return name;}
*/
ListBuffer<JCTree.JCStatement> testStatement3 = new ListBuffer<>();
testStatement3.append(treeMaker.Return(treeMaker.Ident(names.fromString("name"))));
JCTree.JCBlock testBody3 = treeMaker.Block(0, testStatement3.toList());
// 生成入参
JCTree.JCVariableDecl param3 = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER), names.fromString("name"),treeMaker.Ident(names.fromString("String")), null);
com.sun.tools.javac.util.List<JCTree.JCVariableDecl> parameters3 = com.sun.tools.javac.util.List.of(param3);
JCTree.JCMethodDecl test3 = treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), // 办法限定值
names.fromString("test4"), // 办法名
treeMaker.Ident(names.fromString("String")), // 返回类型
com.sun.tools.javac.util.List.nil(),
parameters3, // 入参
com.sun.tools.javac.util.List.nil(),
testBody3,
null
);
非凡的
咱们学完了如何进行定义参数,如何进行定义方法,其实还有好多语句须要学习,例如如何生成 new 语句,如何生成办法调用的语句,如何生成 if 语句。j 接下来咱们就学习一些比拟非凡的语法。
new 一个对象
// 创立一个 new 语句 CombatJCTreeMain combatJCTreeMain = new CombatJCTreeMain();
JCTree.JCNewClass combatJCTreeMain = treeMaker.NewClass(
null,
com.sun.tools.javac.util.List.nil(),
treeMaker.Ident(names.fromString("CombatJCTreeMain")),
com.sun.tools.javac.util.List.nil(),
null
);
JCTree.JCVariableDecl jcVariableDecl1 = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER),
names.fromString("combatJCTreeMain"),
treeMaker.Ident(names.fromString("CombatJCTreeMain")),
combatJCTreeMain
);
办法调用(无参)
JCTree.JCExpressionStatement exec = treeMaker.Exec(
treeMaker.Apply(com.sun.tools.javac.util.List.nil(),
treeMaker.Select(treeMaker.Ident(names.fromString("combatJCTreeMain")), // . 右边的内容
names.fromString("test") // . 左边的内容
),
com.sun.tools.javac.util.List.nil())
);
办法调用(有参)
// 创立一个办法调用 combatJCTreeMain.test2("hello world!");
JCTree.JCExpressionStatement exec2 = treeMaker.Exec(
treeMaker.Apply(com.sun.tools.javac.util.List.nil(),
treeMaker.Select(treeMaker.Ident(names.fromString("combatJCTreeMain")), // . 右边的内容
names.fromString("test2") // . 左边的内容
),
com.sun.tools.javac.util.List.of(treeMaker.Literal("hello world!")) // 办法中的内容
)
);
if 语句
/*
创立一个 if 语句
if("BuXueWuShu".equals(name)){add = "a" + "b";}else{add += "test";}
*/
// "BuXueWuShu".equals(name)
JCTree.JCMethodInvocation apply = treeMaker.Apply(com.sun.tools.javac.util.List.nil(),
treeMaker.Select(treeMaker.Literal("BuXueWuShu"), // . 右边的内容
names.fromString("equals") // . 左边的内容
),
com.sun.tools.javac.util.List.of(treeMaker.Ident(names.fromString("name")))
);
// add = "a" + "b"
JCTree.JCExpressionStatement exec3 = treeMaker.Exec(treeMaker.Assign(treeMaker.Ident(names.fromString("add")), treeMaker.Binary(JCTree.Tag.PLUS, treeMaker.Literal("a"), treeMaker.Literal("b"))));
// add += "test"
JCTree.JCExpressionStatement exec1 = treeMaker.Exec(treeMaker.Assignop(JCTree.Tag.PLUS_ASG, treeMaker.Ident(names.fromString("add")), treeMaker.Literal("test")));
JCTree.JCIf anIf = treeMaker.If(
apply, // if 语句外面的判断语句
exec3, // 条件成立的语句
exec1 // 条件不成立的语句
);
源码地址:https://github.com/modouxians…
总结
纸上得来终觉浅,绝知此事要躬行。
心愿大家看完此篇文章可能本人在本机上本人试验一下。
本人设置几个参数,本人学的 Lombok 学着生成一下 get、set 办法,尽管本篇常识在日常开发中基本上不会用到,然而万一用到了这些常识那么他人不会而你会,差距其实就缓缓的给拉开了。
本篇波及到的所有代码都在 github 下面有,拉下来当前全局搜 CombatJCTreeProcessor
类就能够看到了。
近期热文举荐:
1.1,000+ 道 Java 面试题及答案整顿(2021 最新版)
2. 别在再满屏的 if/ else 了,试试策略模式,真香!!
3. 卧槽!Java 中的 xx ≠ null 是什么新语法?
4.Spring Boot 2.5 重磅公布,光明模式太炸了!
5.《Java 开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞 + 转发哦!