posts - 495,comments - 227,trackbacks - 0
          http://forfuture1978.iteye.com/blog/661680

          3.1、聲明QueryParser類

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

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

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

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

          3.2、聲明詞法分析器

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

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

          //默認處于DEFAULT狀態(tài),當遇到/**的時候,轉(zhuǎn)換為IN_JAVADOC_COMMENT狀態(tài)

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

          //在IN_JAVADOC_COMMENT狀態(tài)下,需要識別@param變量

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

          //在IN_JAVADOC_COMMENT狀態(tài)下,遇到*/的時候,裝換為DEFAULT狀態(tài)

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

          <*> 表示應用于任何狀態(tài)。

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

          <*> TOKEN : {

            <#_NUM_CHAR:   ["0"-"9"] > //數(shù)字

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

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

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

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

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

          }

           

          (2) 默認狀態(tài)的Token

          <DEFAULT> TOKEN : {

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

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

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

          | <PLUS:      "+" >

          | <MINUS:     "-" >

          | <LPAREN:    "(" >

          | <RPAREN:    ")" >

          | <COLON:     ":" >

          | <STAR:      "*" >

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

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

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

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

          | <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是一個小數(shù)

          }

          //包含邊界的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的語法規(guī)則如下:

          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。

              //如果在第一個語句之前出現(xiàn)連接符,則報錯,如"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),也即當僅讀入下一個符號時,不足以判斷接下來的如何解析,會出現(xià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,格式為^加一個數(shù)字

             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優(yōu)先)則為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查詢,則調(diào)用getWildcardQuery,

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

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

                  //    最后生成WildcardQuery

                  //如果是prefix查詢,則調(diào)用getPrefixQuery,生成PrefixQuery

                  //如果是fuzzy查詢,則調(diào)用getFuzzyQuery,生成FuzzyQuery

                  //如果是普通查詢,則調(diào)用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],調(diào)用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},調(diào)用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查詢,調(diào)用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,此兩項將決定到底產(chǎn)生什么樣的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,統(tǒng)計Tokens的個數(shù)numTokens,以及positionIncrement的總數(shù),即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的數(shù)組,屬于同一個數(shù)組的Term表示在同一個位置。它有函數(shù)void add(Term[] terms)一次添加一個數(shù)組的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數(shù)組添加進去,然后用add(new Term[]{new Term("field", "app"), new Term("field", "apple"), new Term("field", "application")})作為一個Term數(shù)組添加進去(算作同一個位置的),則三者都能搜的出來。

                  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已經(jīng)不是同一個位置 了,所以原來收集在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已經(jīng)收集到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;

              }

            }

          }

          posted on 2012-11-12 11:59 SIMONE 閱讀(1220) 評論(0)  編輯  收藏 所屬分類: JAVA
          主站蜘蛛池模板: 镇坪县| 界首市| 日照市| 大洼县| 六枝特区| 哈巴河县| 长乐市| 酉阳| 云梦县| 革吉县| 青州市| 铜梁县| 旬邑县| 乌兰县| 望都县| 响水县| 宿松县| 遂川县| 石渠县| 诏安县| 铁力市| 牙克石市| 肃北| 红原县| 固始县| 哈尔滨市| 昌邑市| 台北市| 镇康县| 卫辉市| 广昌县| 轮台县| 封丘县| 乌鲁木齐县| 正定县| 镇江市| 将乐县| 翁源县| 永寿县| 琼中| 西贡区|