<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 32882字,需瀏覽 66分鐘

           ·

          2021-10-02 01:28

          點擊關注公眾號,Java干貨及時送達

          作者:不學無數的程序員
          來源:https://my.oschina.net/u/4030990/blog/3211858

          在網上關于如何修改Java的抽象語法樹的相關API文檔并不多,于是本篇記錄一下相關的知識點,以便隨后查閱。

          JCTree的介紹

          JCTree是語法樹元素的基類,包含一個重要的字段pos,該字段用于指明當前語法樹節(jié)點(JCTree)在語法樹中的位置,因此我們不能直接用new關鍵字來創(chuàng)建語法樹節(jié)點,即使創(chuàng)建了也沒有意義。

          此外,結合訪問者模式,將數據結構與數據的處理進行解耦,部分源碼如下:

          public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {

              public int pos = -1;

              ...

              public abstract void accept(JCTree.Visitor visitor);

              ...
          }

          我們可以看到JCTree是一個抽象類,這里重點介紹幾個JCTree的子類

          1. JCStatement:聲明語法樹節(jié)點,常見的子類如下

          2. JCMethodDecl:方法定義語法樹節(jié)點

          3. JCModifiers:訪問標志語法樹節(jié)點

          4. JCExpression:表達式語法樹節(jié)點,常見的子類如下

          TreeMaker介紹

          TreeMaker用于創(chuàng)建一系列的語法樹節(jié)點,我們上面說了創(chuàng)建JCTree不能直接使用new關鍵字來創(chuàng)建,所以Java為我們提供了一個工具,就是TreeMaker,它會在創(chuàng)建時為我們創(chuàng)建的JCTree對象設置pos字段,所以必須使用上下文相關的TreeMaker對象來創(chuàng)建語法樹節(jié)點。

          具體的API介紹可以參照,TreeMakerAPI,接下來著重介紹一下常用的幾個方法。

          TreeMaker.Modifiers

          TreeMaker.Modifiers方法用于創(chuàng)建訪問標志語法樹節(jié)點(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用于創(chuàng)建類定義語法樹節(jié)點(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來創(chuàng)建
          2. name:類名
          3. typarams:泛型參數列表
          4. extending:父類
          5. implementing:實現的接口
          6. defs:類定義的詳細語句,包括字段、方法的定義等等

          TreeMaker.MethodDef

          TreeMaker.MethodDef用于創(chuàng)建方法定義語法樹節(jié)點(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類型。另外,Java 系列面試題和答案全部整理好了,微信搜索Java技術棧,在后臺發(fā)送:面試,可以在線閱讀。

          TreeMaker.VarDef

          TreeMaker.VarDef用于創(chuàng)建字段/變量定義語法樹節(jié)點(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用于創(chuàng)建標識符語法樹節(jié)點(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用于創(chuàng)建return語句(JCReturn),源碼如下

          public JCReturn Return(JCExpression expr) {
                  JCReturn tree = new JCReturn(expr);
                  tree.pos = pos;
                  return tree;
          }

          TreeMaker.Select

          TreeMaker.Select用于創(chuàng)建域訪問/方法訪問(這里的方法訪問只是取到名字,方法的調用需要用TreeMaker.Apply)語法樹節(jié)點(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用于創(chuàng)建new語句語法樹節(jié)點(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:待創(chuàng)建對象的類型
          4. args:參數列表
          5. def:類定義

          TreeMaker.Apply

          TreeMaker.Apply用于創(chuàng)建方法調用語法樹節(jié)點(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用戶創(chuàng)建賦值語句語法樹節(jié)點(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用于創(chuàng)建可執(zhí)行語句語法樹節(jié)點(JCExpressionStatement),源碼如下:

          public JCExpressionStatement Exec(JCExpression expr) {
                  JCExpressionStatement tree = new JCExpressionStatement(expr);
                  tree.pos = pos;
                  return tree;
          }

          TreeMaker.Apply以及TreeMaker.Assign就需要外面包一層TreeMaker.Exec來獲得一個JCExpressionStatement。分享:Spring Boot 學習筆記,這個太全了

          TreeMaker.Block

          TreeMaker.Block用于創(chuàng)建組合語句的語法樹節(jié)點(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比較奇怪,是一個鏈式的結構,有頭結點和尾節(jié)點,但是只有尾節(jié)點是一個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用法非常類似。

          點擊關注公眾號,Java干貨及時送達

          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介紹

          這個是為我們創(chuàng)建名稱的一個工具類,無論是類、方法、參數的名稱都需要通過此類來創(chuàng)建。它里面經常被使用到的一個方法就是fromString(),一般使用方法如下所示。

          Names names  = new Names()
          names. fromString("setName");

          實戰(zhàn)演練

          上面我們大概了解了如何操作抽象語法樹,接下來我們就來寫幾個真實的案例加深理解。

          變量相關

          在類中我們經常操作的參數就是變量,那么如何使用抽象語法樹的特性為我們操作變量呢?接下來我們就將一些對于變量的一些操作。

          生成變量

          例如生成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一個對象

          // 創(chuàng)建一個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()
                  )
          );

          方法調用(有參)

          // 創(chuàng)建一個方法調用 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語句

          /*
              創(chuàng)建一個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方法,雖然本篇知識在日常開發(fā)中基本上不會用到,但是萬一用到了這些知識那么別人不會而你會,差距其實就慢慢的給拉開了。

          本篇涉及到的所有代碼都在github上面有,拉下來以后全局搜CombatJCTreeProcessor類就可以看到了。另外,關注公眾號Java技術棧,在后臺回復:面試,可以獲取我整理的 Java 系列面試題和答案,非常齊全。






          關注Java技術棧看更多干貨



          獲取 Spring Boot 實戰(zhàn)筆記!

          瀏覽 37
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产www内射 | 成人毛片18女人毛片免费黑人看 | 中文AV在线播放 | 中文字幕免费MV第一季歌词 | 美女肏逼视频免费看黄色 |