<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    posts - 495,comments - 227,trackbacks - 0
    http://www.cnblogs.com/forfuture1978/archive/2010/05/08/1730201.html

    三、解析QueryParser.jj

     

    3.1、聲明QueryParser類

    在QueryParser.jj文件中,PARSER_BEGIN(QueryParser)和PARSER_END(QueryParser)之間,定義了QueryParser類。

    其中最重要的一個函數是public Query parse(String query)函數,也即我們解析Lucene查詢語法的時候調用的函數。

    這是一個純Java代碼定義的函數,會直接拷貝到QueryParser.java文件中。

    parse函數中,最重要的一行代碼是調用Query res = TopLevelQuery(field),而TopLevelQuery函數是QueryParser.jj中定義的語法分析器被JavaCC編譯后會生成的函數。

    3.2、聲明詞法分析器

    在解析詞法分析器之前,首先介紹一下JavaCC的詞法狀態的概念(lexical state)。

    有可能存在如下的情況,在不同的情況下,要求的詞法詞法規則不同,比如我們要解析一個java文件(即滿足java語法的表達式),在默認的狀態 DEFAULT下,是要求解析的對象(即表達式)滿足java語言的詞法規則,然而當出現"/**"的時候,其后面的表達式則不需要滿足java語言的語 法規則,而是應該滿足java注釋的語法規則(要識別@param變量等),于是我們做如下定義:

    //默認處于DEFAULT狀態,當遇到/**的時候,轉換為IN_JAVADOC_COMMENT狀態

    <DEFAULT> TOKEN : {<STARTDOC : “/**” > : IN_JAVADOC_COMMENT }

    //在IN_JAVADOC_COMMENT狀態下,需要識別@param變量

    <IN_JAVADOC_COMMENT> TOKEN : {<PARAM : "@param" >}

    //在IN_JAVADOC_COMMENT狀態下,遇到*/的時候,裝換為DEFAULT狀態

    <IN_JAVADOC_COMMENT> TOKEN : {<ENDDOC: "*/">: DEFAULT }

    <*> 表示應用于任何狀態。

    (1) 應用于所有狀態的變量

    <*> TOKEN : {

      <#_NUM_CHAR:   ["0"-"9"] > //數字

    | <#_ESCAPED_CHAR: "\\" ~[] > //"\"后的任何一個字符都是被轉義的

    | <#_TERM_START_CHAR: ( ~[ " ", "\t", "\n", "\r", "\u3000", "+", "-", "!", "(", ")", ":", "^", "[", "]", "\"", "{", "}", "~", "*", "?", "\\" ] | <_ESCAPED_CHAR> ) > //表達式中任何一個term,都不能以[]括起來的列表中的lucene查詢語法關鍵字開頭,當然被轉義的除外。

    | <#_TERM_CHAR: ( <_TERM_START_CHAR> | <_ESCAPED_CHAR> | "-" | "+" ) > //表達式中的term非起始字符,可以包含任何非語法關鍵字字符,轉義過的字符,也可以包含+, -(但包含+,-的符合詞法,不合語法)。

    | <#_WHITESPACE: ( " " | "\t" | "\n" | "\r" | "\u3000") > //被認為是空格的字符

    | <#_QUOTED_CHAR: ( ~[ "\"", "\\" ] | <_ESCAPED_CHAR> ) > //被引號括起來的字符不應再包括"和\,當然轉義過的除外。

    }

     

    (2) 默認狀態的Token

    <DEFAULT> TOKEN : {

      <AND:       ("AND" | "&&") >

    | <OR:        ("OR" | "||") >

    | <NOT:       ("NOT" | "!") >

    | <PLUS:      "+" >

    | <MINUS:     "-" >

    | <LPAREN:    "(" >

    | <RPAREN:    ")" >

    | <COLON:     ":" >

    | <STAR:      "*" >

    | <CARAT:     "^" > : Boost //當遇到^的時候,后面跟隨的是boost表達式,進入Boost狀態

    | <QUOTED:     "\"" (<_QUOTED_CHAR>)* "\"">

    | <TERM:      <_TERM_START_CHAR> (<_TERM_CHAR>)*  >

    | <FUZZY_SLOP:     "~" ( (<_NUM_CHAR>)+ ( "." (<_NUM_CHAR>)+ )? )? > //Fuzzy查詢,~后面跟小數。

    | <PREFIXTERM:  ("*") | ( <_TERM_START_CHAR> (<_TERM_CHAR>)* "*" ) > //使用*進行Prefix查詢,可以盡包含*,或者末尾包含*,然而只包含*符合詞法,不合語法。

    | <WILDTERM:  (<_TERM_START_CHAR> | [ "*", "?" ]) (<_TERM_CHAR> | ( [ "*", "?" ] ))* > //使用*和?進行wildcard查詢

    | <RANGEIN_START: "[" > : RangeIn //遇到[]的時候,是包含邊界的Range查詢

    | <RANGEEX_START: "{" > : RangeEx //遇到{}的時候,是不包含邊界的Range查詢

    }

    <Boost> TOKEN : {

    <NUMBER:    (<_NUM_CHAR>)+ ( "." (<_NUM_CHAR>)+ )? > : DEFAULT //boost是一個小數

    }

    //包含邊界的Range查詢是[A TO B]的形式。

    <RangeIn> TOKEN : {

    <RANGEIN_TO: "TO">

    | <RANGEIN_END: "]"> : DEFAULT

    | <RANGEIN_QUOTED: "\"" (~["\""] | "\\\"")+ "\"">

    | <RANGEIN_GOOP: (~[ " ", "]" ])+ >

    }

    //不包含邊界的Range查詢是{A TO B}的形式

    <RangeEx> TOKEN : {

    <RANGEEX_TO: "TO">

    | <RANGEEX_END: "}"> : DEFAULT

    | <RANGEEX_QUOTED: "\"" (~["\""] | "\\\"")+ "\"">

    | <RANGEEX_GOOP: (~[ " ", "}" ])+ >

    }

     

    3.3、聲明語法分析器

    Lucene的語法規則如下:

    Query  ::= ( Clause )*

    Clause ::= ["+", "-"] [<TERM> ":"] ( <TERM> | "(" Query ")" )

    (1) 從Query到Clause

    一個Query查詢語句,是由多個clause組成的,每個clause有修飾符Modifier,或為+, 或為-,clause之間的有連接符,或為AND,或為OR,或為NOT。

    在Lucene的語法解析中NOT被算作Modifier,和-起相同作用。

    此過程表達式如下:

    Query TopLevelQuery(String field) :

    {

        Query q;

    }

    {

        q=Query(field) <EOF>

        {

            return q;

        }

    }

    Query Query(String field) :

    {

      List<BooleanClause> clauses = new ArrayList<BooleanClause>();

      Query q, firstQuery=null;

      int conj, mods;

    }

    {

      //查詢語句開頭是一個Modifier,可以為空

      //Modifier后面便是子語句clause,可以生成子查詢語句q

      mods=Modifiers() q=Clause(field)

      {

        //如果第一個語句的Modifier是空,則將子查詢q付給firstQuery,從后面我們可以看到,當只有一個查詢 語句的時候,如果其Modifier為空,則不返回BooleanQuery,而是返回子查詢對象firstQuery。從這里我們可以看出,如果查詢語 句為"A",則生成TermQuery,其term為"A",如果查詢語句為"+A",則生成BooleanQuery,其子查詢只有一個,就是 TermQuery,其term為"A"。

        addClause(clauses, CONJ_NONE, mods, q);

        if (mods == MOD_NONE)

            firstQuery=q;

      }

      (

        //除了第一個語句外,其他的前面可以有連接符,或為AND,或為OR。

        //如果在第一個語句之前出現連接符,則報錯,如"OR a",會報Encountered " <OR> "OR "" at line 1, column 0.

        //除了連接符,也會有Modifier,后面是子語句clause,生成子查詢q,并加入BooleanQuery中。

        conj=Conjunction() mods=Modifiers() q=Clause(field)

        { addClause(clauses, conj, mods, q); }

      )*

      {

        //如果只有一個查詢語句,且其modifier為空,則返回firstQuery,否則由所有的子語句clause,生成BooleanQuery。

        if (clauses.size() == 1 && firstQuery != null)

          return firstQuery;

        else {

          return getBooleanQuery(clauses);

        }

      }

    }

    int Modifiers() : {

      //默認modifier為空,如果遇到+,就是required,如果遇到-或者NOT,就是prohibited。

      int ret = MOD_NONE;

    }

    {

      [

         <PLUS> { ret = MOD_REQ; }

         | <MINUS> { ret = MOD_NOT; }

         | <NOT> { ret = MOD_NOT; }

      ]

      { return ret; }

    }

    //連接符

    int Conjunction() : {

      int ret = CONJ_NONE;

    }

    {

      [

        <AND> { ret = CONJ_AND; }

        | <OR>  { ret = CONJ_OR; }

      ]

      { return ret; }

    }

     

    (2) 一個子語句clause

    由上面的分析我們可以知道,JavaCC使用的是編譯原理里面的自上而下分析法,基本采用的是LL(1)的方法:

    • 第一個L :從左到右掃描輸入串
    • 第二個L :生成的是最左推導
    • (1):向前看一個輸入符號(lookahead)

    JavaCC還提供LOOKAHEAD(n),也即當僅讀入下一個符號時,不足以判斷接下來的如何解析,會出現Choice Conflict,則需要多讀入幾個符號,來進一步判斷。

     

    Query Clause(String field) : {

      Query q;

      Token fieldToken=null, boost=null;

    }

    {

      //此處之所以向前看兩個符號,就是當看到<TERM>的時候,不知道它是一個field,還是一個term,當<TERM><COLON>在一起的時候,說明<TERM>代表一個field, 否則代表一個term

      [

        LOOKAHEAD(2)

        (

        fieldToken=<TERM> <COLON> {field=discardEscapeChar(fieldToken.image);}

        | <STAR> <COLON> {field="*";}

        )

      ]

      (

      //或者是一個term,則由此term生成一個查詢對象

       //或者是一個由括號括起來的子查詢

       //()?表示可能存在一個boost,格式為^加一個數字

       q=Term(field)

       | <LPAREN> q=Query(field) <RPAREN> (<CARAT> boost=<NUMBER>)?

      )

      {

        //如果存在boost,則設定查詢對象的boost

        if (boost != null) {

          float f = (float)1.0;

          try {

            f = Float.valueOf(boost.image).floatValue();

            q.setBoost(f);

          } catch (Exception ignored) { }

        }

        return q;

      }

    }

     

    Query Term(String field) : {

      Token term, boost=null, fuzzySlop=null, goop1, goop2;

      boolean prefix = false;

      boolean wildcard = false;

      boolean fuzzy = false;

      Query q;

    }

    {

      (

         (

          //如果term僅結尾包含*則是prefix查詢。

           //如果以*開頭,或者中間包含*,或者結尾包含*(如果僅結尾包含,則prefix優先)則為wildcard查詢。

           term=<TERM>

           | term=<STAR> { wildcard=true; }

           | term=<PREFIXTERM> { prefix=true; }

           | term=<WILDTERM> { wildcard=true; }

           | term=<NUMBER>

         )

         //如果term后面是~,則是fuzzy查詢

         [ fuzzySlop=<FUZZY_SLOP> { fuzzy=true; } ]

         [ <CARAT> boost=<NUMBER> [ fuzzySlop=<FUZZY_SLOP> { fuzzy=true; } ] ]

         {

            //如果是wildcard查詢,則調用getWildcardQuery,

            //    *:*得到MatchAllDocsQuery,將返回所有的文檔

            //    目前不支持最前面帶通配符的查詢(雖然詞法分析和語法分析都能通過),否則報ParseException

            //    最后生成WildcardQuery

            //如果是prefix查詢,則調用getPrefixQuery,生成PrefixQuery

            //如果是fuzzy查詢,則調用getFuzzyQuery,生成FuzzyQuery

            //如果是普通查詢,則調用getFieldQuery

           String termImage=discardEscapeChar(term.image);

           if (wildcard) {

             q = getWildcardQuery(field, termImage);

           } else if (prefix) {

             q = getPrefixQuery(field, discardEscapeChar(term.image.substring(0, term.image.length()-1)));

           } else if (fuzzy) {

             float fms = fuzzyMinSim;

             try {

               fms = Float.valueOf(fuzzySlop.image.substring(1)).floatValue();

             } catch (Exception ignored) { }

             if(fms < 0.0f || fms > 1.0f){

               throw new ParseException("Minimum similarity for a FuzzyQuery has to be between 0.0f and 1.0f !");

             }

             q = getFuzzyQuery(field, termImage,fms);

           } else {

             q = getFieldQuery(field, termImage);

           }

         }

         //包含邊界的range查詢,取得[goop1 TO goop2],調用getRangeQuery,生成TermRangeQuery

         | ( <RANGEIN_START> ( goop1=<RANGEIN_GOOP>|goop1=<RANGEIN_QUOTED> )

             [ <RANGEIN_TO> ] ( goop2=<RANGEIN_GOOP>|goop2=<RANGEIN_QUOTED> )

             <RANGEIN_END> )

           [ <CARAT> boost=<NUMBER> ]

            {

              if (goop1.kind == RANGEIN_QUOTED) {

                goop1.image = goop1.image.substring(1, goop1.image.length()-1);

              }

              if (goop2.kind == RANGEIN_QUOTED) {

                goop2.image = goop2.image.substring(1, goop2.image.length()-1);

              }

              q = getRangeQuery(field, discardEscapeChar(goop1.image), discardEscapeChar(goop2.image), true);

            }

         //不包含邊界的range查詢,取得{goop1 TO goop2},調用getRangeQuery,生成TermRangeQuery

         | ( <RANGEEX_START> ( goop1=<RANGEEX_GOOP>|goop1=<RANGEEX_QUOTED> )

             [ <RANGEEX_TO> ] ( goop2=<RANGEEX_GOOP>|goop2=<RANGEEX_QUOTED> )

             <RANGEEX_END> )

           [ <CARAT> boost=<NUMBER> ]

            {

              if (goop1.kind == RANGEEX_QUOTED) {

                goop1.image = goop1.image.substring(1, goop1.image.length()-1);

              }

              if (goop2.kind == RANGEEX_QUOTED) {

                goop2.image = goop2.image.substring(1, goop2.image.length()-1);

              }

              q = getRangeQuery(field, discardEscapeChar(goop1.image), discardEscapeChar(goop2.image), false);

            }

         //被""括起來的term,得到phrase查詢,調用getFieldQuery

         | term=<QUOTED>

           [ fuzzySlop=<FUZZY_SLOP> ]

           [ <CARAT> boost=<NUMBER> ]

           {

             int s = phraseSlop;

             if (fuzzySlop != null) {

               try {

                 s = Float.valueOf(fuzzySlop.image.substring(1)).intValue();

               }

               catch (Exception ignored) { }

             }

             q = getFieldQuery(field, discardEscapeChar(term.image.substring(1, term.image.length()-1)), s);

           }

      )

      {

        if (boost != null) {

          float f = (float) 1.0;

          try {

            f = Float.valueOf(boost.image).floatValue();

          }

          catch (Exception ignored) {

          }

          // avoid boosting null queries, such as those caused by stop words

          if (q != null) {

            q.setBoost(f);

          }

        }

        return q;

      }

    }

     

    此處需要詳細解析的是getFieldQuery:

    protected Query getFieldQuery(String field, String queryText)  throws ParseException {

      //需要用analyzer對文本進行分詞

      TokenStream source;

      try {

        source = analyzer.reusableTokenStream(field, new StringReader(queryText));

        source.reset();

      } catch (IOException e) {

        source = analyzer.tokenStream(field, new StringReader(queryText));

      }

      CachingTokenFilter buffer = new CachingTokenFilter(source);

      TermAttribute termAtt = null;

      PositionIncrementAttribute posIncrAtt = null;

      int numTokens = 0;

      boolean success = false;

      try {

        buffer.reset();

        success = true;

      } catch (IOException e) {

      }

      //得到TermAttribute和PositionIncrementAttribute,此兩項將決定到底產生什么樣的Query對象

      if (success) {

        if (buffer.hasAttribute(TermAttribute.class)) {

          termAtt = buffer.getAttribute(TermAttribute.class);

        }

        if (buffer.hasAttribute(PositionIncrementAttribute.class)) {

          posIncrAtt = buffer.getAttribute(PositionIncrementAttribute.class);

        }

      }

      int positionCount = 0;

      boolean severalTokensAtSamePosition = false;

      boolean hasMoreTokens = false;

      if (termAtt != null) {

        try {

          //遍歷分詞后的所有Token,統計Tokens的個數numTokens,以及positionIncrement的總數,即positionCount。

          //當有一次positionIncrement為0的時候,severalTokensAtSamePosition設為true,表示有多個Token處在同一個位置。

          hasMoreTokens = buffer.incrementToken();

          while (hasMoreTokens) {

            numTokens++;

            int positionIncrement = (posIncrAtt != null) ? posIncrAtt.getPositionIncrement() : 1;

            if (positionIncrement != 0) {

              positionCount += positionIncrement;

            } else {

              severalTokensAtSamePosition = true;

            }

            hasMoreTokens = buffer.incrementToken();

          }

        } catch (IOException e) {

        }

      }

      try {

        //重設buffer,以便生成phrase查詢的時候,term和position可以重新遍歷。

        buffer.reset();

        source.close();

      }

      catch (IOException e) {

      }

      if (numTokens == 0)

        return null;

      else if (numTokens == 1) {

        //如果分詞后只有一個Token,則生成TermQuery

        String term = null;

        try {

          boolean hasNext = buffer.incrementToken();

          term = termAtt.term();

        } catch (IOException e) {

        }

        return newTermQuery(new Term(field, term));

      } else {

       //如果分詞后不只有一個Token

        if (severalTokensAtSamePosition) {

       //如果有多個Token處于同一個位置

          if (positionCount == 1) {

            //并且處于同一位置的Token還全部處于第一個位置,則生成BooleanQuery,處于同一位置的Token之間是OR的關系

            BooleanQuery q = newBooleanQuery(true);

            for (int i = 0; i < numTokens; i++) {

              String term = null;

              try {

                boolean hasNext = buffer.incrementToken();

                term = termAtt.term();

              } catch (IOException e) {

              }

              Query currentQuery = newTermQuery(new Term(field, term));

              q.add(currentQuery, BooleanClause.Occur.SHOULD);

            }

            return q;

          }

          else {

            //如果有多個Token處于同一位置,但不是第一個位置,則生成MultiPhraseQuery。

            //所謂MultiPhraseQuery即其可以包含多個phrase,其又一個ArrayList<Term[]> termArrays,每一項都是一個Term的數組,屬于同一個數組的Term表示在同一個位置。它有函數void add(Term[] terms)一次添加一個數組的Term。比如我們要搜索"microsoft app*",其表示多個phrase,"microsoft apple","microsoft application"都算。此時用QueryParser.parse("\"microsoft app*\"")從而生成PhraseQuery是搜不出microsoft apple和microsoft application的,也不能搜出microsoft app,因為*一旦被引號所引,就不算通配符了。所以必須生成MultiPhraseQuery,首先用add(new Term[]{new Term("field", "microsoft")})將microsoft作為一個Term數組添加進去,然后用add(new Term[]{new Term("field", "app"), new Term("field", "apple"), new Term("field", "application")})作為一個Term數組添加進去(算作同一個位置的),則三者都能搜的出來。

            MultiPhraseQuery mpq = newMultiPhraseQuery();

            mpq.setSlop(phraseSlop);

            List<Term> multiTerms = new ArrayList<Term>();

            int position = -1;

            for (int i = 0; i < numTokens; i++) {

              String term = null;

              int positionIncrement = 1;

              try {

                boolean hasNext = buffer.incrementToken();

                assert hasNext == true;

                term = termAtt.term();

                if (posIncrAtt != null) {

                  positionIncrement = posIncrAtt.getPositionIncrement();

                }

              } catch (IOException e) {

              }

              if (positionIncrement > 0 && multiTerms.size() > 0) {

                //如果positionIncrement大于零,說明此Term和前一個Term已經不是同一個位置 了,所以原來收集在multiTerms中的Term都算作同一個位置,添加到MultiPhraseQuery中作為一項。并清除 multiTerms,以便重新收集相同位置的Term。

                if (enablePositionIncrements) {

                  mpq.add(multiTerms.toArray(new Term[0]),position);

                } else {

                  mpq.add(multiTerms.toArray(new Term[0]));

                }

                multiTerms.clear();

              }

              //將此Term收集到multiTerms中。

              position += positionIncrement;

              multiTerms.add(new Term(field, term));

            }

            //當遍歷完所有的Token,同處于最后一個位置的Term已經收集到multiTerms中了,把他們加到MultiPhraseQuery中作為一項。

            if (enablePositionIncrements) {

              mpq.add(multiTerms.toArray(new Term[0]),position);

            } else {

              mpq.add(multiTerms.toArray(new Term[0]));

            }

            return mpq;

          }

        }

        else {

          //如果不存在多個Token處于同一個位置的情況,則直接生成PhraseQuery

          PhraseQuery pq = newPhraseQuery();

          pq.setSlop(phraseSlop);

          int position = -1;

          for (int i = 0; i < numTokens; i++) {

            String term = null;

            int positionIncrement = 1;

            try {

              boolean hasNext = buffer.incrementToken();

              assert hasNext == true;

              term = termAtt.term();

              if (posIncrAtt != null) {

                positionIncrement = posIncrAtt.getPositionIncrement();

              }

            } catch (IOException e) {

            }

            if (enablePositionIncrements) {

              position += positionIncrement;

              pq.add(new Term(field, term),position);

            } else {

              pq.add(new Term(field, term));

            }

          }

          return pq;

        }

      }

    }

     

     

    ----------------------------------------------------------------------------------------------------------

    相關文章:

     

    Lucene學習總結之一:全文檢索的基本原理

    http://www.cnblogs.com/forfuture1978/archive/2009/12/14/1623594.html

    Lucene學習總結之二:Lucene的總體架構

    http://www.cnblogs.com/forfuture1978/archive/2009/12/14/1623596.html 

    Lucene學習總結之三:Lucene的索引文件格式(1)

    http://www.cnblogs.com/forfuture1978/archive/2009/12/14/1623597.html

    Lucene學習總結之三:Lucene的索引文件格式(2)

    http://www.cnblogs.com/forfuture1978/archive/2009/12/14/1623599.html

    Lucene學習總結之三:Lucene的索引文件格式(3)

    http://www.cnblogs.com/forfuture1978/archive/2010/02/02/1661436.html

    Lucene學習總結之四:Lucene索引過程分析(1)

    http://www.cnblogs.com/forfuture1978/archive/2010/02/02/1661439.html

    Lucene學習總結之四:Lucene索引過程分析(2)

    http://www.cnblogs.com/forfuture1978/archive/2010/02/02/1661440.html

    Lucene學習總結之四:Lucene索引過程分析(3)

    http://www.cnblogs.com/forfuture1978/archive/2010/02/02/1661441.html

    Lucene學習總結之四:Lucene索引過程分析(4) 

    http://www.cnblogs.com/forfuture1978/archive/2010/02/02/1661442.html 

    Lucene學習總結之五:Lucene段合并(merge)過程分析

    http://www.cnblogs.com/forfuture1978/archive/2010/03/06/1679501.html

    Lucene學習總結之六:Lucene打分公式的數學推導

    http://www.cnblogs.com/forfuture1978/archive/2010/03/07/1680007.html

    Lucene學習總結之七:Lucene搜索過程解析(1)

    http://www.cnblogs.com/forfuture1978/archive/2010/04/04/1704242.html

    Lucene學習總結之七:Lucene搜索過程解析(2)

    http://www.cnblogs.com/forfuture1978/archive/2010/04/04/1704245.html

    Lucene學習總結之七:Lucene搜索過程解析(3)

    http://www.cnblogs.com/forfuture1978/archive/2010/04/04/1704250.html

    Lucene學習總結之七:Lucene搜索過程解析(4)

    http://www.cnblogs.com/forfuture1978/archive/2010/04/04/1704254.html

    Lucene學習總結之七:Lucene搜索過程解析(5)

    http://www.cnblogs.com/forfuture1978/archive/2010/04/04/1704258.html

    Lucene學習總結之七:Lucene搜索過程解析(6)

    http://www.cnblogs.com/forfuture1978/archive/2010/04/04/1704263.html

     

    posted on 2015-04-21 18:52 SIMONE 閱讀(276) 評論(0)  編輯  收藏

    只有注冊用戶登錄后才能發表評論。


    網站導航:
     
    主站蜘蛛池模板: 深夜久久AAAAA级毛片免费看| 99久久精品免费精品国产| 亚洲精品~无码抽插| AV无码免费永久在线观看| 理论亚洲区美一区二区三区| 亚洲成AV人片在线观看无码| 大地资源二在线观看免费高清| 九九免费精品视频在这里| 亚洲精品成人网站在线播放| 免费人成网站在线高清| 91短视频在线免费观看| 四虎国产精品成人免费久久 | 亚洲精品自拍视频| 国产一区二区三区在线免费观看| 久久综合九色综合97免费下载| 蜜芽亚洲av无码一区二区三区 | 久久亚洲精精品中文字幕| 国产午夜鲁丝片AV无码免费| 久久精品视频免费看| 成人免费观看男女羞羞视频| 91嫩草私人成人亚洲影院| 亚洲午夜日韩高清一区| 在线精品免费视频| 免费播放一区二区三区| eeuss影院免费92242部| 亚洲av永久无码精品网址| 亚洲欧洲校园自拍都市| 国产精品亚洲成在人线| 青青青国产色视频在线观看国产亚洲欧洲国产综合 | 亚洲国产精品一区二区三区久久 | 性生大片视频免费观看一级| 亚洲一级毛片中文字幕| 亚洲第一AAAAA片| 国产精品亚洲综合专区片高清久久久| 成年女人免费视频播放体验区| 永久在线观看免费视频| 亚洲免费日韩无码系列| 色费女人18女人毛片免费视频| 国产精品高清视亚洲一区二区 | 亚洲国产精品免费视频| 亚洲成A人片在线观看无码不卡|