add --shared
[pylucene.git] / lucene-java-3.4.0 / lucene / src / java / org / apache / lucene / search / spans / SpanMultiTermQueryWrapper.java
1 package org.apache.lucene.search.spans;
2
3 /**
4  * Licensed to the Apache Software Foundation (ASF) under one or more
5  * contributor license agreements.  See the NOTICE file distributed with
6  * this work for additional information regarding copyright ownership.
7  * The ASF licenses this file to You under the Apache License, Version 2.0
8  * (the "License"); you may not use this file except in compliance with
9  * the License.  You may obtain a copy of the License at
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19
20 import java.io.IOException;
21 import java.lang.reflect.Method;
22
23 import org.apache.lucene.index.IndexReader;
24 import org.apache.lucene.index.Term;
25 import org.apache.lucene.search.MultiTermQuery;
26 import org.apache.lucene.search.Query;
27 import org.apache.lucene.search.TopTermsRewrite;
28 import org.apache.lucene.search.ScoringRewrite;
29 import org.apache.lucene.search.BooleanClause.Occur; // javadocs only
30
31 /**
32  * Wraps any {@link MultiTermQuery} as a {@link SpanQuery}, 
33  * so it can be nested within other SpanQuery classes.
34  * <p>
35  * The query is rewritten by default to a {@link SpanOrQuery} containing
36  * the expanded terms, but this can be customized. 
37  * <p>
38  * Example:
39  * <blockquote><pre>
40  * {@code
41  * WildcardQuery wildcard = new WildcardQuery(new Term("field", "bro?n"));
42  * SpanQuery spanWildcard = new SpanMultiTermQueryWrapper<WildcardQuery>(wildcard);
43  * // do something with spanWildcard, such as use it in a SpanFirstQuery
44  * }
45  * </pre></blockquote>
46  */
47 public class SpanMultiTermQueryWrapper<Q extends MultiTermQuery> extends SpanQuery {
48   protected final Q query;
49   private Method getFieldMethod = null, getTermMethod = null;
50
51   /**
52    * Create a new SpanMultiTermQueryWrapper. 
53    * 
54    * @param query Query to wrap.
55    * <p>
56    * NOTE: This will call {@link MultiTermQuery#setRewriteMethod(MultiTermQuery.RewriteMethod)}
57    * on the wrapped <code>query</code>, changing its rewrite method to a suitable one for spans.
58    * Be sure to not change the rewrite method on the wrapped query afterwards! Doing so will
59    * throw {@link UnsupportedOperationException} on rewriting this query!
60    * In Lucene 3.x, MultiTermQuery allows queries to rewrite to different field names, but SpanQuery
61    * needs a fixed field. The wrapped query must therefore support getField() or getTerm().
62    * @throws IllegalArgumentException if the wrapped query does not provide getField() or getTerm().
63    */
64   public SpanMultiTermQueryWrapper(Q query) {
65     this.query = query;
66     
67     MultiTermQuery.RewriteMethod method = query.getRewriteMethod();
68     if (method instanceof TopTermsRewrite) {
69       final int pqsize = ((TopTermsRewrite) method).getSize();
70       setRewriteMethod(new TopTermsSpanBooleanQueryRewrite(pqsize));
71     } else {
72       setRewriteMethod(SCORING_SPAN_QUERY_REWRITE); 
73     }
74     
75     // In Lucene 3.x, MTQ has no fixed field, we need to get it by reflection.
76     // If the underlying query does not allow to get a constant field, we throw IAE:
77     try {
78       getFieldMethod = query.getClass().getMethod("getField");
79     } catch (Exception e1) {
80       try {
81         getTermMethod = query.getClass().getMethod("getTerm");
82       } catch (Exception e2) {
83         try {
84           getTermMethod = query.getClass().getMethod("getPrefix");
85         } catch (Exception e3) {
86           throw new IllegalArgumentException("SpanMultiTermQueryWrapper can only wrap MultiTermQueries"+
87             " that can return a field name using getField() or getTerm()");
88         }
89       }
90     }
91   }
92   
93   /**
94    * Expert: returns the rewriteMethod
95    */
96   public final SpanRewriteMethod getRewriteMethod() {
97     final MultiTermQuery.RewriteMethod m = query.getRewriteMethod();
98     if (!(m instanceof SpanRewriteMethod))
99       throw new UnsupportedOperationException("You can only use SpanMultiTermQueryWrapper with a suitable SpanRewriteMethod.");
100     return (SpanRewriteMethod) m;
101   }
102
103   /**
104    * Expert: sets the rewrite method. This only makes sense
105    * to be a span rewrite method.
106    */
107   public final void setRewriteMethod(SpanRewriteMethod rewriteMethod) {
108     query.setRewriteMethod(rewriteMethod);
109   }
110   
111   @Override
112   public Spans getSpans(IndexReader reader) throws IOException {
113     throw new UnsupportedOperationException("Query should have been rewritten");
114   }
115
116   @Override
117   public String getField() {
118     try {
119       if (getFieldMethod != null) {
120         return (String) getFieldMethod.invoke(query);
121       } else {
122         assert getTermMethod != null;
123         return ((Term) getTermMethod.invoke(query)).field();
124       }
125     } catch (Exception e) {
126       throw new RuntimeException("Cannot invoke getField() or getTerm() on wrapped query.", e);
127     }
128   }
129
130   @Override
131   public String toString(String field) {
132     StringBuilder builder = new StringBuilder();
133     builder.append("SpanMultiTermQueryWrapper(");
134     builder.append(query.toString(field));
135     builder.append(")");
136     return builder.toString();
137   }
138
139   @Override
140   public Query rewrite(IndexReader reader) throws IOException {
141     final Query q = query.rewrite(reader);
142     if (!(q instanceof SpanQuery))
143       throw new UnsupportedOperationException("You can only use SpanMultiTermQueryWrapper with a suitable SpanRewriteMethod.");
144     return q;
145   }
146   
147   @Override
148   public int hashCode() {
149     return 31 * query.hashCode();
150   }
151
152   @Override
153   public boolean equals(Object obj) {
154     if (this == obj) return true;
155     if (obj == null) return false;
156     if (getClass() != obj.getClass()) return false;
157     final SpanMultiTermQueryWrapper other = (SpanMultiTermQueryWrapper) obj;
158     return query.equals(other.query);
159   }
160
161   /** Abstract class that defines how the query is rewritten. */
162   public static abstract class SpanRewriteMethod extends MultiTermQuery.RewriteMethod {
163     @Override
164     public abstract SpanQuery rewrite(IndexReader reader, MultiTermQuery query) throws IOException;
165   }
166
167   /**
168    * A rewrite method that first translates each term into a SpanTermQuery in a
169    * {@link Occur#SHOULD} clause in a BooleanQuery, and keeps the
170    * scores as computed by the query.
171    * 
172    * @see #setRewriteMethod
173    */
174   public final static SpanRewriteMethod SCORING_SPAN_QUERY_REWRITE = new SpanRewriteMethod() {
175     private final ScoringRewrite<SpanOrQuery> delegate = new ScoringRewrite<SpanOrQuery>() {
176       @Override
177       protected SpanOrQuery getTopLevelQuery() {
178         return new SpanOrQuery();
179       }
180
181       @Override
182       protected void addClause(SpanOrQuery topLevel, Term term, float boost) {
183         final SpanTermQuery q = new SpanTermQuery(term);
184         q.setBoost(boost);
185         topLevel.addClause(q);
186       }
187     };
188     
189     @Override
190     public SpanQuery rewrite(IndexReader reader, MultiTermQuery query) throws IOException {
191       return delegate.rewrite(reader, query);
192     }
193
194     // Make sure we are still a singleton even after deserializing
195     protected Object readResolve() {
196       return SCORING_SPAN_QUERY_REWRITE;
197     }
198   };
199   
200   /**
201    * A rewrite method that first translates each term into a SpanTermQuery in a
202    * {@link Occur#SHOULD} clause in a BooleanQuery, and keeps the
203    * scores as computed by the query.
204    * 
205    * <p>
206    * This rewrite method only uses the top scoring terms so it will not overflow
207    * the boolean max clause count.
208    * 
209    * @see #setRewriteMethod
210    */
211   public static final class TopTermsSpanBooleanQueryRewrite extends SpanRewriteMethod  {
212     private final TopTermsRewrite<SpanOrQuery> delegate;
213   
214     /** 
215      * Create a TopTermsSpanBooleanQueryRewrite for 
216      * at most <code>size</code> terms.
217      */
218     public TopTermsSpanBooleanQueryRewrite(int size) {
219       delegate = new TopTermsRewrite<SpanOrQuery>(size) {
220         @Override
221         protected int getMaxSize() {
222           return Integer.MAX_VALUE;
223         }
224     
225         @Override
226         protected SpanOrQuery getTopLevelQuery() {
227           return new SpanOrQuery();
228         }
229
230         @Override
231         protected void addClause(SpanOrQuery topLevel, Term term, float boost) {
232           final SpanTermQuery q = new SpanTermQuery(term);
233           q.setBoost(boost);
234           topLevel.addClause(q);
235         }
236       };
237     }
238     
239     /** return the maximum priority queue size */
240     public int getSize() {
241       return delegate.getSize();
242     }
243
244     @Override
245     public SpanQuery rewrite(IndexReader reader, MultiTermQuery query) throws IOException {
246       return delegate.rewrite(reader, query);
247     }
248   
249     @Override
250     public int hashCode() {
251       return 31 * delegate.hashCode();
252     }
253
254     @Override
255     public boolean equals(Object obj) {
256       if (this == obj) return true;
257       if (obj == null) return false;
258       if (getClass() != obj.getClass()) return false;
259       final TopTermsSpanBooleanQueryRewrite other = (TopTermsSpanBooleanQueryRewrite) obj;
260       return delegate.equals(other.delegate);
261     }
262     
263   }
264   
265 }