Java中的屠龍之術——如何修改語法樹

Lombok常常用,可是你知道它的原理是什麼嗎?,和Lombok常常用,可是你知道它的原理是什麼嗎?(二)兩篇文章中介紹了關於Lombok的底層原理,其實總結爲一句話來講就是在編譯期經過改變抽象語法樹而實現的。上面兩篇文章已經講了抽象語法樹的相關知識點,若是有不清楚的能夠看一下。html

本篇涉及到的全部代碼都在github上面有java

本篇涉及到的全部代碼都在github上面有git

本篇涉及到的全部代碼都在github上面有github

在網上關於如何修改Java的抽象語法樹的相關API文檔並很少,因而本篇記錄一下相關的知識點,以便隨後查閱。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的子類app

  1. JCStatement:聲明語法樹節點,常見的子類以下
    • JCBlock:語句塊語法樹節點
    • JCReturn:return語句語法樹節點
    • JCClassDecl:類定義語法樹節點
    • JCVariableDecl:字段/變量定義語法樹節點
  2. JCMethodDecl:方法定義語法樹節點
  3. JCModifiers:訪問標誌語法樹節點
  4. JCExpression:表達式語法樹節點,常見的子類以下
    • JCAssign:賦值語句語法樹節點
    • JCIdent:標識符語法樹節點,能夠是變量,類型,關鍵字等等

TreeMaker介紹

TreeMaker用於建立一系列的語法樹節點,咱們上面說了建立JCTree不能直接使用new關鍵字來建立,因此Java爲咱們提供了一個工具,就是TreeMaker,它會在建立時爲咱們建立的JCTree對象設置pos字段,因此必須使用上下文相關的TreeMaker對象來建立語法樹節點。工具

具體的API介紹能夠參照,TreeMakerAPI,接下來着重介紹一下經常使用的幾個方法。post

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;
}
  1. flags:訪問標誌
  2. 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;
}
  1. mods:訪問標誌,能夠經過TreeMaker.Modifiers來建立
  2. name:類名
  3. typarams:泛型參數列表
  4. extending:父類
  5. implementing:實現的接口
  6. 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);
}
  1. mods:訪問標誌
  2. name:方法名
  3. restype:返回類型
  4. typarams:泛型參數列表
  5. params:參數列表
  6. thrown:異常聲明列表
  7. body:方法體
  8. defaultValue:默認方法(多是interface中的哪一個default)
  9. m:方法符號
  10. 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);
}
  1. mods:訪問標誌
  2. name:參數名稱
  3. vartype:類型
  4. init:初始化語句
  5. 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);
}
  1. selected:.運算符左邊的表達式
  2. 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;
}
  1. encl:不太明白此參數的含義,我看不少例子中此參數都設置爲null
  2. typeargs:參數類型列表
  3. clazz:待建立對象的類型
  4. args:參數列表
  5. 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;
}
  1. typeargs:參數類型列表
  2. fn:調用語句
  3. args:參數列表

TreeMaker.Assign

TreeMaker.Assign用戶建立賦值語句語法樹節點(JCAssign),源碼以下:

ublic JCAssign Assign(JCExpression lhs,
    JCExpression rhs) {
        JCAssign tree = new JCAssign(lhs, rhs);
        tree.pos = pos;
        return tree;
}
  1. lhs:賦值語句左邊表達式
  2. 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;
}
  1. flags:訪問標誌
  2. 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  // 條件不成立的語句
);

源碼地址

總結

紙上得來終覺淺,絕知此事要躬行。但願你們看完此篇文章可以本身在本機上本身試驗一下。本身設置幾個參數,本身學的Lombok學着生成一下get、set方法,雖然本篇知識在平常開發中基本上不會用到,可是萬一用到了這些知識那麼別人不會而你會,差距其實就慢慢的給拉開了。本篇涉及到的全部代碼都在github上面有,拉下來之後全局搜CombatJCTreeProcessor類就能夠看到了。

相關文章
相關標籤/搜索