BooleanQuery.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 import gate.creole.annic.apache.lucene.index.IndexReader;
022 
023 /** A Query that matches documents matching boolean combinations of other
024   queries, typically {@link TermQuery}s or {@link PhraseQuery}s.
025   */
026 @SuppressWarnings({"serial","rawtypes","unchecked"})
027 public class BooleanQuery extends Query {
028 
029   /**
030    * Default value is 1024.  Use <code>gate.creole.annic.apache.lucene.maxClauseCount</code>
031    * system property to override.
032    */
033   public static int maxClauseCount =
034     Integer.parseInt(System.getProperty("gate.creole.annic.apache.lucene.maxClauseCount",
035       "1024"));
036 
037   /** Thrown when an attempt is made to add more than {@link
038    * #getMaxClauseCount()} clauses. */
039   public static class TooManyClauses extends RuntimeException {}
040 
041   /** Return the maximum number of clauses permitted, 1024 by default.
042    * Attempts to add more than the permitted number of clauses cause {@link
043    * TooManyClauses} to be thrown.*/
044   public static int getMaxClauseCount() { return maxClauseCount; }
045 
046   /** Set the maximum number of clauses permitted. */
047   public static void setMaxClauseCount(int maxClauseCount) {
048     BooleanQuery.maxClauseCount = maxClauseCount;
049   }
050 
051   private Vector clauses = new Vector();
052 
053   /** Constructs an empty boolean query. */
054   public BooleanQuery() {}
055 
056   /** Adds a clause to a boolean query.  Clauses may be:
057    <ul>
058    <li><code>required</code> which means that documents which <i>do not</i>
059    * match this sub-query will <i>not</i> match the boolean query;
060    <li><code>prohibited</code> which means that documents which <i>do</i>
061    * match this sub-query will <i>not</i> match the boolean query; or
062    <li>neither, in which case matched documents are neither prohibited from
063    * nor required to match the sub-query. However, a document must match at
064    * least 1 sub-query to match the boolean query.
065    </ul>
066    * It is an error to specify a clause as both <code>required</code> and
067    <code>prohibited</code>.
068    *
069    @see #getMaxClauseCount()
070    */
071   public void add(Query query, boolean required, boolean prohibited) {
072     add(new BooleanClause(query, required, prohibited));
073   }
074 
075   /** Adds a clause to a boolean query.
076     @see #getMaxClauseCount()
077    */
078   public void add(BooleanClause clause) {
079     if (clauses.size() >= maxClauseCount)
080       throw new TooManyClauses();
081 
082     clauses.addElement(clause);
083   }
084 
085   /** Returns the set of clauses in this query. */
086   public BooleanClause[] getClauses() {
087     return (BooleanClause[])clauses.toArray(new BooleanClause[0]);
088   }
089 
090   private class BooleanWeight implements Weight {
091     private Searcher searcher;
092     private Vector weights = new Vector();
093 
094     public BooleanWeight(Searcher searcher) {
095       this.searcher = searcher;
096       for (int i = ; i < clauses.size(); i++) {
097         BooleanClause c = (BooleanClause)clauses.elementAt(i);
098         weights.add(c.query.createWeight(searcher));
099       }
100     }
101 
102     @Override
103     public Query getQuery() { return BooleanQuery.this}
104     @Override
105     public float getValue() { return getBoost()}
106 
107     @Override
108     public float sumOfSquaredWeights() throws IOException {
109       float sum = 0.0f;
110       for (int i = ; i < weights.size(); i++) {
111         BooleanClause c = (BooleanClause)clauses.elementAt(i);
112         Weight w = (Weight)weights.elementAt(i);
113         if (!c.prohibited)
114           sum += w.sumOfSquaredWeights();         // sum sub weights
115       }
116 
117       sum *= getBoost() * getBoost();             // boost each sub-weight
118 
119       return sum ;
120     }
121 
122 
123     @Override
124     public void normalize(float norm) {
125       norm *= getBoost();                         // incorporate boost
126       for (int i = ; i < weights.size(); i++) {
127         BooleanClause c = (BooleanClause)clauses.elementAt(i);
128         Weight w = (Weight)weights.elementAt(i);
129         if (!c.prohibited)
130           w.normalize(norm);
131       }
132     }
133 
134     @Override
135     public Scorer scorer(IndexReader reader, Searcher searcherthrows IOException {
136       this.searcher = searcher;
137       // First see if the (faster) ConjunctionScorer will work.  This can be
138       // used when all clauses are required.  Also, at this point a
139       // BooleanScorer cannot be embedded in a ConjunctionScorer, as the hits
140       // from a BooleanScorer are not always sorted by document number (sigh)
141       // and hence BooleanScorer cannot implement skipTo() correctly, which is
142       // required by ConjunctionScorer.
143       boolean allRequired = true;
144       boolean noneBoolean = true;
145       for (int i = ; i < weights.size(); i++) {
146         BooleanClause c = (BooleanClause)clauses.elementAt(i);
147         if (!c.required)
148           allRequired = false;
149         if (c.query instanceof BooleanQuery)
150           noneBoolean = false;
151       }
152 
153       if (allRequired && noneBoolean) {           // ConjunctionScorer is okay
154         ConjunctionScorer result =
155           new ConjunctionScorer(getSimilarity(searcher));
156         for (int i = ; i < weights.size(); i++) {
157           Weight w = (Weight)weights.elementAt(i);
158           Scorer subScorer = w.scorer(reader, searcher);
159           if (subScorer == null)
160             return null;
161           result.add(subScorer);
162         }
163         return result;
164       }
165 
166       // Use good-old BooleanScorer instead.
167       BooleanScorer result = new BooleanScorer(getSimilarity(searcher));
168 
169       for (int i = ; i < weights.size(); i++) {
170         BooleanClause c = (BooleanClause)clauses.elementAt(i);
171         Weight w = (Weight)weights.elementAt(i);
172         Scorer subScorer = w.scorer(reader, searcher);
173         if (subScorer != null)
174           result.add(subScorer, c.required, c.prohibited);
175         else if (c.required)
176           return null;
177       }
178 
179       return result;
180     }
181 
182     @Override
183     public Explanation explain(IndexReader reader, int doc)
184       throws IOException {
185       Explanation sumExpl = new Explanation();
186       sumExpl.setDescription("sum of:");
187       int coord = 0;
188       int maxCoord = 0;
189       float sum = 0.0f;
190       for (int i = ; i < weights.size(); i++) {
191         BooleanClause c = (BooleanClause)clauses.elementAt(i);
192         Weight w = (Weight)weights.elementAt(i);
193         Explanation e = w.explain(reader, doc);
194         if (!c.prohibitedmaxCoord++;
195         if (e.getValue() 0) {
196           if (!c.prohibited) {
197             sumExpl.addDetail(e);
198             sum += e.getValue();
199             coord++;
200           else {
201             return new Explanation(0.0f"match prohibited");
202           }
203         else if (c.required) {
204           return new Explanation(0.0f"match required");
205         }
206       }
207       sumExpl.setValue(sum);
208 
209       if (coord == 1)                               // only one clause matched
210         sumExpl = sumExpl.getDetails()[0];          // eliminate wrapper
211 
212       float coordFactor = getSimilarity(searcher).coord(coord, maxCoord);
213       if (coordFactor == 1.0f)                      // coord is no-op
214         return sumExpl;                             // eliminate wrapper
215       else {
216         Explanation result = new Explanation();
217         result.setDescription("product of:");
218         result.addDetail(sumExpl);
219         result.addDetail(new Explanation(coordFactor,
220                                          "coord("+coord+"/"+maxCoord+")"));
221         result.setValue(sum*coordFactor);
222         return result;
223       }
224     }
225   }
226 
227   @Override
228   protected Weight createWeight(Searcher searcher) {
229     return new BooleanWeight(searcher);
230   }
231 
232   @Override
233   public Query rewrite(IndexReader readerthrows IOException {
234     if (clauses.size() == 1) {                    // optimize 1-clause queries
235       BooleanClause c = (BooleanClause)clauses.elementAt(0);
236       if (!c.prohibited) {        // just return clause
237 
238         Query query = c.query.rewrite(reader);    // rewrite first
239 
240         if (getBoost() != 1.0f) {                 // incorporate boost
241           if (query == c.query)                   // if rewrite was no-op
242             query = (Query)query.clone();         // then clone before boost
243           query.setBoost(getBoost() * query.getBoost());
244         }
245 
246         return query;
247       }
248     }
249 
250     BooleanQuery clone = null;                    // recursively rewrite
251     for (int i = ; i < clauses.size(); i++) {
252       BooleanClause c = (BooleanClause)clauses.elementAt(i);
253       Query query = c.query.rewrite(reader);
254       if (query != c.query) {                     // clause rewrote: must clone
255         if (clone == null)
256           clone = (BooleanQuery)this.clone();
257         clone.clauses.setElementAt
258           (new BooleanClause(query, c.required, c.prohibited), i);
259       }
260     }
261     if (clone != null) {
262       return clone;                               // some clauses rewrote
263     else
264       return this;                                // no clauses rewrote
265   }
266 
267 
268   @Override
269   public Object clone() {
270     BooleanQuery clone = (BooleanQuery)super.clone();
271     clone.clauses = (Vector)this.clauses.clone();
272     return clone;
273   }
274 
275   /** Prints a user-readable version of this query. */
276   @Override
277   public String toString(String field) {
278     StringBuffer buffer = new StringBuffer();
279     if (getBoost() != 1.0) {
280       buffer.append("(");
281     }
282 
283     for (int i = ; i < clauses.size(); i++) {
284       BooleanClause c = (BooleanClause)clauses.elementAt(i);
285       if (c.prohibited)
286   buffer.append("-");
287       else if (c.required)
288   buffer.append("+");
289 
290       Query subQuery = c.query;
291       if (subQuery instanceof BooleanQuery) {    // wrap sub-bools in parens
292   buffer.append("(");
293   buffer.append(c.query.toString(field));
294   buffer.append(")");
295       else
296   buffer.append(c.query.toString(field));
297 
298       if (i != clauses.size()-1)
299   buffer.append(" ");
300     }
301 
302     if (getBoost() != 1.0) {
303       buffer.append(")^");
304       buffer.append(getBoost());
305     }
306 
307     return buffer.toString();
308   }
309 
310   /** Returns true iff <code>o</code> is equal to this. */
311   @Override
312   public boolean equals(Object o) {
313     if (!(instanceof BooleanQuery))
314       return false;
315     BooleanQuery other = (BooleanQuery)o;
316     return (this.getBoost() == other.getBoost())
317       &&  this.clauses.equals(other.clauses);
318   }
319 
320   /** Returns a hash code value for this object.*/
321   @Override
322   public int hashCode() {
323     return Float.floatToIntBits(getBoost()) ^ clauses.hashCode();
324   }
325 
326 }