<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 中的屠龍之術(shù):如何修改語法樹?

          共 16324字,需瀏覽 33分鐘

           ·

          2021-12-18 16:06


          作者:不學(xué)無數(shù)的程序員

          來源:https://my.oschina.net/u/4030990/blog/3211858

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

          JCTree的介紹

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

          此外,結(jié)合訪問者模式,將數(shù)據(jù)結(jié)構(gòu)與數(shù)據(jù)的處理進(jìn)行解耦,部分源碼如下:

          public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {

          public int pos = -1;

          ...

          public abstract void accept(JCTree.Visitor visitor);

          ...
          }

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

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

            • JCBlock:語句塊語法樹節(jié)點(diǎn)

            • JCReturn:return語句語法樹節(jié)點(diǎn)

            • JCClassDecl:類定義語法樹節(jié)點(diǎn)

            • JCVariableDecl:字段/變量定義語法樹節(jié)點(diǎn)

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

          3. JCModifiers:訪問標(biāo)志語法樹節(jié)點(diǎn)

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

            • JCAssign:賦值語句語法樹節(jié)點(diǎn)

            • JCIdent:標(biāo)識符語法樹節(jié)點(diǎn),可以是變量,類型,關(guān)鍵字等等

          TreeMaker介紹

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

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

          TreeMaker.Modifiers

          TreeMaker.Modifiers方法用于創(chuàng)建訪問標(biāo)志語法樹節(jié)點(diǎn)(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;
          }
          1. flags:訪問標(biāo)志

          2. annotations:注解列表

          其中flags可以使用枚舉類com.sun.tools.javac.code.Flags來表示,例如我們可以這樣用,就生成了下面的訪問標(biāo)志了。

          treeMaker.Modifiers(Flags.PUBLIC + Flags.STATIC + Flags.FINAL);

          public static final

          TreeMaker.ClassDef

          TreeMaker.ClassDef用于創(chuàng)建類定義語法樹節(jié)點(diǎn)(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;
          }
          1. mods:訪問標(biāo)志,可以通過TreeMaker.Modifiers來創(chuàng)建

          2. name:類名

          3. typarams:泛型參數(shù)列表

          4. extending:父類

          5. implementing:實現(xiàn)的接口

          6. defs:類定義的詳細(xì)語句,包括字段、方法的定義等等

          TreeMaker.MethodDef

          TreeMaker.MethodDef用于創(chuàng)建方法定義語法樹節(jié)點(diǎn)(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);
          }
          1. mods:訪問標(biāo)志

          2. name:方法名

          3. restype:返回類型

          4. typarams:泛型參數(shù)列表

          5. params:參數(shù)列表

          6. thrown:異常聲明列表

          7. body:方法體

          8. defaultValue:默認(rèn)方法(可能是interface中的哪個default)

          9. m:方法符號

          10. mtype:方法類型。包含多種類型,泛型參數(shù)類型、方法參數(shù)類型、異常參數(shù)類型、返回參數(shù)類型。

          返回類型restype填寫null或者treeMaker.TypeIdent(TypeTag.VOID)都代表返回void類型

          TreeMaker.VarDef

          TreeMaker.VarDef用于創(chuàng)建字段/變量定義語法樹節(jié)點(diǎn)(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:訪問標(biāo)志

          2. name:參數(shù)名稱

          3. vartype:類型

          4. init:初始化語句

          5. v:變量符號

          TreeMaker.Ident

          TreeMaker.Ident用于創(chuàng)建標(biāo)識符語法樹節(jié)點(diǎn)(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)建域訪問/方法訪問(這里的方法訪問只是取到名字,方法的調(diào)用需要用TreeMaker.Apply)語法樹節(jié)點(diǎn)(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:.運(yùn)算符左邊的表達(dá)式

          2. selector:.運(yùn)算符右邊的表達(dá)式

          下面給出一個例子,一語句生成的Java語句就是二語句

          一. TreeMaker.Select(treeMaker.Ident(names.fromString("this")), names.fromString("name"));

          二. this.name

          TreeMaker.NewClass

          TreeMaker.NewClass用于創(chuàng)建new語句語法樹節(jié)點(diǎn)(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;
          }
          1. encl:不太明白此參數(shù)的含義,我看很多例子中此參數(shù)都設(shè)置為null

          2. typeargs:參數(shù)類型列表

          3. clazz:待創(chuàng)建對象的類型

          4. args:參數(shù)列表

          5. def:類定義

          TreeMaker.Apply

          TreeMaker.Apply用于創(chuàng)建方法調(diào)用語法樹節(jié)點(diǎn)(JCMethodInvocation),源碼如下:

          public JCMethodInvocation Apply(List typeargs,
          JCExpression fn,
          List args) {
          JCMethodInvocation tree = new JCMethodInvocation(typeargs, fn, args);
          tree.pos = pos;
          return tree;
          }
          1. typeargs:參數(shù)類型列表

          2. fn:調(diào)用語句

          3. args:參數(shù)列表

          TreeMaker.Assign

          TreeMaker.Assign用戶創(chuàng)建賦值語句語法樹節(jié)點(diǎn)(JCAssign),源碼如下:

          ublic JCAssign Assign(JCExpression lhs,
          JCExpression rhs) {
          JCAssign tree = new JCAssign(lhs, rhs);
          tree.pos = pos;
          return tree;
          }
          1. lhs:賦值語句左邊表達(dá)式

          2. rhs:賦值語句右邊表達(dá)式

          TreeMaker.Exec

          TreeMaker.Exec用于創(chuàng)建可執(zhí)行語句語法樹節(jié)點(diǎn)(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用于創(chuàng)建組合語句的語法樹節(jié)點(diǎn)(JCBlock),源碼如下:

          public JCBlock Block(long flags,
          List stats) {
          JCBlock tree = new JCBlock(flags, stats);
          tree.pos = pos;
          return tree;
          }
          1. flags:訪問標(biāo)志

          2. stats:語句列表

          com.sun.tools.javac.util.List介紹

          在我們操作抽象語法樹的時候,有時會涉及到關(guān)于List的操作,但是這個List不是我們經(jīng)常使用的java.util.List而是com.sun.tools.javac.util.List,這個List比較奇怪,是一個鏈?zhǔn)降慕Y(jié)構(gòu),有頭結(jié)點(diǎn)和尾節(jié)點(diǎn),但是只有尾節(jié)點(diǎn)是一個List,這里作為了解就行了。

          public class List extends AbstractCollection implements java.util.List {
          public A head;
          public List
          tail;
          private static final List EMPTY_LIST = new List((Object)null, (List)null) {
          public List setTail(List var1) {
          throw new UnsupportedOperationException();
          }

          public boolean isEmpty() {
          return true;
          }
          };

          List(A head, List tail) {
          this.tail = tail;
          this.head = head;
          }

          public static
          List nil() {
          return EMPTY_LIST;
          }

          public List
          prepend(A var1) {
          return new List(var1, this);
          }

          public List
          append(A var1) {
          return of(var1).prependList(this);
          }

          public static
          List of(A var0) {
          return new List(var0, nil());
          }

          public static
          List of(A var0, A var1) {
          return new List(var0, of(var1));
          }

          public static
          List of(A var0, A var1, A var2) {
          return new List(var0, of(var1, var2));
          }

          public static
          List 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,此類的操作和我們平時經(jīng)常使用的java.util.List用法非常類似。

          public class ListBuffer extends AbstractQueue {

          public static ListBuffer of(T x) {
          ListBuffer lb = new ListBuffer();
          lb.add(x);
          return lb;
          }

          /** The list of elements of this buffer.
          */
          private List
          elems;

          /** A pointer pointing to the last element of 'elems' containing data,
          * or null if the list is empty.
          */
          private List
          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
          append(A x) {
          x.getClass(); // null check
          if (shared) copy();
          List
          newLast = List.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)建名稱的一個工具類,無論是類、方法、參數(shù)的名稱都需要通過此類來創(chuàng)建。它里面經(jīng)常被使用到的一個方法就是fromString(),一般使用方法如下所示。

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

          實戰(zhàn)演練

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

          變量相關(guān)

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

          生成變量

          例如生成private String age;這樣一個變量,借用我們上面講的VarDef方法

          // 生成參數(shù) 例如: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"))))

          方法相關(guān)

          我們對于變量進(jìn)行了操作,那么基本上都是要生成方法的,那么如何對方法進(jìn)行生成和操作呢?我們接下來演示一下關(guān)于方法相關(guān)的操作方法。

          無參無返回值

          我們可以利用上面講到的MethodDef方法進(jìn)行生成

          /*
          無參無返回值的方法生成
          public void test(){

          }
          */
          // 定義方法體
          ListBuffer 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方法進(jìn)行生成

          /*
          無參無返回值的方法生成
          public void test2(String name){
          name = "xxxx";
          }
          */
          ListBuffer 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());

          // 生成入?yún)?br>JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER), names.fromString("name"),treeMaker.Ident(names.fromString("String")), null);
          com.sun.tools.javac.util.List 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, // 入?yún)?br> com.sun.tools.javac.util.List.nil(),
          testBody2,
          null
          );

          有參有返回值

           /*
          有參有返回值
          public String test3(String name){
          return name;
          }
          */

          ListBuffer testStatement3 = new ListBuffer<>();
          testStatement3.append(treeMaker.Return(treeMaker.Ident(names.fromString("name"))));
          JCTree.JCBlock testBody3 = treeMaker.Block(0, testStatement3.toList());

          // 生成入?yún)?br>JCTree.JCVariableDecl param3 = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER), names.fromString("name"),treeMaker.Ident(names.fromString("String")), null);
          com.sun.tools.javac.util.List 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, // 入?yún)?br> com.sun.tools.javac.util.List.nil(),
          testBody3,
          null
          );

          特殊的

          我們學(xué)完了如何進(jìn)行定義參數(shù),如何進(jìn)行定義方法,其實還有好多語句需要學(xué)習(xí),例如如何生成new語句,如何生成方法調(diào)用的語句,如何生成if語句。j接下來我們就學(xué)習(xí)一些比較特殊的語法。

          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
          );

          方法調(diào)用(無參)

          JCTree.JCExpressionStatement exec = treeMaker.Exec(
          treeMaker.Apply(
          com.sun.tools.javac.util.List.nil(),
          treeMaker.Select(
          treeMaker.Ident(names.fromString("combatJCTreeMain")), // . 左邊的內(nèi)容
          names.fromString("test") // . 右邊的內(nèi)容
          ),
          com.sun.tools.javac.util.List.nil()
          )
          );

          方法調(diào)用(有參)

          // 創(chuàng)建一個方法調(diào)用 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")), // . 左邊的內(nèi)容
          names.fromString("test2") // . 右邊的內(nèi)容
          ),
          com.sun.tools.javac.util.List.of(treeMaker.Literal("hello world!")) // 方法中的內(nèi)容
          )
          );

          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"), // . 左邊的內(nèi)容
          names.fromString("equals") // . 右邊的內(nèi)容
          ),
          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

          總結(jié)

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

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

          自己設(shè)置幾個參數(shù),自己學(xué)的Lombok學(xué)著生成一下get、set方法,雖然本篇知識在日常開發(fā)中基本上不會用到,但是萬一用到了這些知識那么別人不會而你會,差距其實就慢慢的給拉開了。

          本篇涉及到的所有代碼都在github上面有,拉下來以后全局搜CombatJCTreeProcessor類就可以看到了。

          —?【 THE END 】—
          公眾號[程序員黃小斜]全部博文已整理成一個目錄,請在公眾號里回復(fù)「m」獲??!

          最近面試BAT,整理一份面試資料Java面試BATJ通關(guān)手冊,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。

          獲取方式:點(diǎn)“在看”,關(guān)注公眾號并回復(fù) PDF?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。

          文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。

          謝謝支持喲 (*^__^*)

          瀏覽 36
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                    aⅴ黄色电影 | 俺去也天天幹 | 国产免费又粗又大又硬又爽视频 | 成人三级片网站视频 | 国产美女在线观看 |