PhraseQuery.java
001 package gate.creole.annic.apache.lucene.search;
002 
003 /**
004  * Copyright 2004 The Apache Software Foundation
005  *
006  * Licensed under the Apache License, Version 2.0 (the "License");
007  * you may not use this file except in compliance with the License.
008  * You may obtain a copy of the License at
009  *
010  *     http://www.apache.org/licenses/LICENSE-2.0
011  *
012  * Unless required by applicable law or agreed to in writing, software
013  * distributed under the License is distributed on an "AS IS" BASIS,
014  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015  * See the License for the specific language governing permissions and
016  * limitations under the License.
017  */
018 
019 import java.io.IOException;
020 import java.util.Vector;
021 
022 import gate.creole.annic.apache.lucene.index.Term;
023 import gate.creole.annic.apache.lucene.index.TermPositions;
024 import gate.creole.annic.apache.lucene.index.IndexReader;
025 
026 /** A Query that matches documents containing a particular sequence of terms.
027   This may be combined with other terms with a {@link BooleanQuery}.
028   */
029 @SuppressWarnings({"serial","rawtypes","unchecked"})
030 public class PhraseQuery extends Query {
031   private String field;
032   private Vector terms = new Vector();
033   /* Niraj */
034   private Vector positions = new Vector();
035   /* End */
036   private int slop = 0;
037 
038   /** Constructs an empty phrase query. */
039   public PhraseQuery() {}
040 
041   /** Sets the number of other words permitted between words in query phrase.
042     If zero, then this is an exact phrase search.  For larger values this works
043     like a <code>WITHIN</code> or <code>NEAR</code> operator.
044 
045     <p>The slop is in fact an edit-distance, where the units correspond to
046     moves of terms in the query phrase out of position.  For example, to switch
047     the order of two words requires two moves (the first move places the words
048     atop one another), so to permit re-orderings of phrases, the slop must be
049     at least two.
050 
051     <p>More exact matches are scored higher than sloppier matches, thus search
052     results are sorted by exactness.
053 
054     <p>The slop is zero by default, requiring exact matches.*/
055   public void setSlop(int s) { slop = s; }
056   /** Returns the slop.  See setSlop(). */
057   public int getSlop() { return slop; }
058 
059   int totalTerms = 0;
060   /** Niraj */
061   /** Adds a term to the end of the query phrase. */
062   public void add(Term term, Integer position, boolean considerAsATerm) {
063     if (terms.size() == 0)
064       field = term.field();
065     else if (term.field() != field)
066       throw new IllegalArgumentException
067   ("All phrase terms must be in the same field: " + term);
068 
069     terms.addElement(term);
070     positions.addElement(position);
071 
072     if(considerAsATerm) {
073       totalTerms++;
074     }
075   }
076 
077   public void setTotalTerms(int totalTerms) {
078     this.totalTerms = totalTerms;
079   }
080 
081   /* End */
082 
083   public void add(Term term) {
084     if (terms.size() == 0)
085       field = term.field();
086     else if (term.field() != field)
087       throw new IllegalArgumentException
088         ("All phrase terms must be in the same field: " + term);
089 
090     terms.addElement(term);
091   }
092 
093   /** Returns the set of terms in this phrase. */
094   public Term[] getTerms() {
095     return (Term[])terms.toArray(new Term[0]);
096   }
097 
098   private class PhraseWeight implements Weight {
099     private Searcher searcher;
100     private float value;
101     private float idf;
102     private float queryNorm;
103     private float queryWeight;
104 
105     public PhraseWeight(Searcher searcher) {
106       this.searcher = searcher;
107     }
108 
109     @Override
110     public String toString() { return "weight(" + PhraseQuery.this ")"}
111 
112     @Override
113     public Query getQuery() { return PhraseQuery.this}
114     @Override
115     public float getValue() { return value; }
116 
117     @Override
118     public float sumOfSquaredWeights() throws IOException {
119       idf = getSimilarity(searcher).idf(terms, searcher);
120       queryWeight = idf * getBoost();             // compute query weight
121       return queryWeight * queryWeight;           // square it
122     }
123 
124     @Override
125     public void normalize(float queryNorm) {
126       this.queryNorm = queryNorm;
127       queryWeight *= queryNorm;                   // normalize query weight
128       value = queryWeight * idf;                  // idf for document
129     }
130 
131     @Override
132     public Scorer scorer(IndexReader reader, Searcher searcherthrows IOException {
133         if (terms.size() == 0)        // optimize zero-term case
134             return null;
135 
136           TermPositions[] tps = new TermPositions[terms.size()];
137           for (int i = 0; i < terms.size(); i++) {
138             TermPositions p = reader.termPositions((Term)terms.elementAt(i));
139             if (p == null)
140               return null;
141             tps[i= p;
142           }
143 
144           if (slop == 0)  {        // optimize exact case
145             return new ExactPhraseScorer(this, tps, /*Niraj*/positions, totalTerms, getSimilarity(searcher),
146                                          reader.norms(field), searcher);
147 
148           }
149           else
150             return
151               new SloppyPhraseScorer(this, tps, getSimilarity(searcher), slop,
152                                      reader.norms(field));
153 
154     }
155     
156 
157     @Override
158     public Explanation explain(IndexReader reader, int doc)
159       throws IOException {
160 
161       Explanation result = new Explanation();
162       result.setDescription("weight("+getQuery()+" in "+doc+"), product of:");
163 
164       StringBuffer docFreqs = new StringBuffer();
165       StringBuffer query = new StringBuffer();
166       query.append('\"');
167       for (int i = 0; i < terms.size(); i++) {
168         if (i != 0) {
169           docFreqs.append(" ");
170           query.append(" ");
171         }
172 
173         Term term = (Term)terms.elementAt(i);
174 
175         docFreqs.append(term.text());
176         docFreqs.append("=");
177         docFreqs.append(searcher.docFreq(term));
178 
179         query.append(term.text());
180       }
181       query.append('\"');
182 
183       Explanation idfExpl =
184         new Explanation(idf, "idf(" + field + ": " + docFreqs + ")");
185 
186       // explain query weight
187       Explanation queryExpl = new Explanation();
188       queryExpl.setDescription("queryWeight(" + getQuery() "), product of:");
189 
190       Explanation boostExpl = new Explanation(getBoost()"boost");
191       if (getBoost() != 1.0f)
192         queryExpl.addDetail(boostExpl);
193       queryExpl.addDetail(idfExpl);
194 
195       Explanation queryNormExpl = new Explanation(queryNorm,"queryNorm");
196       queryExpl.addDetail(queryNormExpl);
197 
198       queryExpl.setValue(boostExpl.getValue() *
199                          idfExpl.getValue() *
200                          queryNormExpl.getValue());
201 
202       result.addDetail(queryExpl);
203 
204       // explain field weight
205       Explanation fieldExpl = new Explanation();
206       fieldExpl.setDescription("fieldWeight("+field+":"+query+" in "+doc+
207                                "), product of:");
208 
209       Explanation tfExpl = scorer(reader, this.searcher).explain(doc);
210       fieldExpl.addDetail(tfExpl);
211       fieldExpl.addDetail(idfExpl);
212 
213       Explanation fieldNormExpl = new Explanation();
214       byte[] fieldNorms = reader.norms(field);
215       float fieldNorm =
216         fieldNorms!=null ? Similarity.decodeNorm(fieldNorms[doc]) 0.0f;
217       fieldNormExpl.setValue(fieldNorm);
218       fieldNormExpl.setDescription("fieldNorm(field="+field+", doc="+doc+")");
219       fieldExpl.addDetail(fieldNormExpl);
220 
221       fieldExpl.setValue(tfExpl.getValue() *
222                          idfExpl.getValue() *
223                          fieldNormExpl.getValue());
224 
225       result.addDetail(fieldExpl);
226 
227       // combine them
228       result.setValue(queryExpl.getValue() * fieldExpl.getValue());
229 
230       if (queryExpl.getValue() == 1.0f)
231         return fieldExpl;
232 
233       return result;
234     }
235   }
236 
237   @Override
238   protected Weight createWeight(Searcher searcher) {
239     if (terms.size() == 1) {        // optimize one-term case
240       Term term = (Term)terms.elementAt(0);
241       Query termQuery = new TermQuery(term);
242       termQuery.setBoost(getBoost());
243       return termQuery.createWeight(searcher);
244     }
245     return new PhraseWeight(searcher);
246   }
247 
248 
249   /** Prints a user-readable version of this query. */
250   @Override
251   public String toString(String f) {
252     StringBuffer buffer = new StringBuffer();
253     if (!field.equals(f)) {
254       buffer.append(field);
255       buffer.append(":");
256     }
257 
258     buffer.append("\"");
259     for (int i = 0; i < terms.size(); i++) {
260       buffer.append(((Term)terms.elementAt(i)).text());
261       if (i != terms.size()-1)
262   buffer.append(" ");
263     }
264     buffer.append("\"");
265 
266     if (slop != 0) {
267       buffer.append("~");
268       buffer.append(slop);
269     }
270 
271     if (getBoost() != 1.0f) {
272       buffer.append("^");
273       buffer.append(Float.toString(getBoost()));
274     }
275 
276     return buffer.toString();
277   }
278 
279   /** Returns true iff <code>o</code> is equal to this. */
280   @Override
281   public boolean equals(Object o) {
282     if (!(instanceof PhraseQuery))
283       return false;
284     PhraseQuery other = (PhraseQuery)o;
285     return (this.getBoost() == other.getBoost())
286       && (this.slop == other.slop)
287       &&  this.terms.equals(other.terms);
288   }
289 
290   /** Returns a hash code value for this object.*/
291   @Override
292   public int hashCode() {
293     return Float.floatToIntBits(getBoost())
294       ^ Float.floatToIntBits(slop)
295       ^ terms.hashCode();
296   }
297 
298 }