天天看點

Java 中的屠龍之術:如何修改文法樹?

作者:不學無數的程式員

來源:

https://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的子類

  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,接下來着重介紹一下常用的幾個方法。

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. params:參數清單
  5. thrown:異常聲明清單
  6. body:方法體
  7. defaultValue:預設方法(可能是interface中的哪個default)
  8. m:方法符号
  9. 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. name:參數名稱
  2. vartype:類型
  3. init:初始化語句
  4. 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. fn:調用語句

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. 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/modouxiansheng/Doraemon

總結

紙上得來終覺淺,絕知此事要躬行。

希望大家看完此篇文章能夠自己在本機上自己試驗一下。

自己設定幾個參數,自己學的Lombok學着生成一下get、set方法,雖然本篇知識在日常開發中基本上不會用到,但是萬一用到了這些知識那麼别人不會而你會,差距其實就慢慢的給拉開了。

本篇涉及到的所有代碼都在github上面有,拉下來以後全局搜

CombatJCTreeProcessor

類就可以看到了。