1 package org.apache.lucene.search;
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.text.Collator;
22
23 import org.apache.lucene.index.Term;
24 import org.apache.lucene.index.TermEnum;
25 import org.apache.lucene.index.IndexReader;
26 import org.apache.lucene.util.ToStringUtils;
27
28 /**
29 * A Query that matches documents within an exclusive range. A RangeQuery
30 * is built by QueryParser for input like <code>[010 TO 120]</code> but only if the QueryParser has
31 * the useOldRangeQuery property set to true. The QueryParser default behaviour is to use
32 * the newer ConstantScoreRangeQuery class. This is generally preferable because:
33 * <ul>
34 * <li>It is faster than RangeQuery</li>
35 * <li>Unlike RangeQuery, it does not cause a BooleanQuery.TooManyClauses exception if the range of values is large</li>
36 * <li>Unlike RangeQuery it does not influence scoring based on the scarcity of individual terms that may match</li>
37 * </ul>
38 *
39 *
40 * @see ConstantScoreRangeQuery
41 *
42 *
43 * @version $Id: RangeQuery.java 696056 2008-09-16 21:03:21Z gsingers $
44 */
45 public class RangeQuery extends Query
46 {
47 private Term lowerTerm;
48 private Term upperTerm;
49 private boolean inclusive;
50 private Collator collator;
51
52 /** Constructs a query selecting all terms greater than
53 * <code>lowerTerm</code> but less than <code>upperTerm</code>.
54 * There must be at least one term and either term may be null,
55 * in which case there is no bound on that side, but if there are
56 * two terms, both terms <b>must</b> be for the same field.
57 *
58 * @param lowerTerm The Term at the lower end of the range
59 * @param upperTerm The Term at the upper end of the range
60 * @param inclusive If true, both <code>lowerTerm</code> and
61 * <code>upperTerm</code> will themselves be included in the range.
62 */
63 public RangeQuery(Term lowerTerm, Term upperTerm, boolean inclusive)
64 {
65 if (lowerTerm == null && upperTerm == null)
66 {
67 throw new IllegalArgumentException("At least one term must be non-null");
68 }
69 if (lowerTerm != null && upperTerm != null && lowerTerm.field() != upperTerm.field())
70 {
71 throw new IllegalArgumentException("Both terms must be for the same field");
72 }
73
74 // if we have a lowerTerm, start there. otherwise, start at beginning
75 if (lowerTerm != null) {
76 this.lowerTerm = lowerTerm;
77 }
78 else {
79 this.lowerTerm = new Term(upperTerm.field());
80 }
81
82 this.upperTerm = upperTerm;
83 this.inclusive = inclusive;
84 }
85
86 /** Constructs a query selecting all terms greater than
87 * <code>lowerTerm</code> but less than <code>upperTerm</code>.
88 * There must be at least one term and either term may be null,
89 * in which case there is no bound on that side, but if there are
90 * two terms, both terms <b>must</b> be for the same field.
91 * <p>
92 * If <code>collator</code> is not null, it will be used to decide whether
93 * index terms are within the given range, rather than using the Unicode code
94 * point order in which index terms are stored.
95 * <p>
96 * <strong>WARNING:</strong> Using this constructor and supplying a non-null
97 * value in the <code>collator</code> parameter will cause every single
98 * index Term in the Field referenced by lowerTerm and/or upperTerm to be
99 * examined. Depending on the number of index Terms in this Field, the
100 * operation could be very slow.
101 *
102 * @param lowerTerm The Term at the lower end of the range
103 * @param upperTerm The Term at the upper end of the range
104 * @param inclusive If true, both <code>lowerTerm</code> and
105 * <code>upperTerm</code> will themselves be included in the range.
106 * @param collator The collator to use to collate index Terms, to determine
107 * their membership in the range bounded by <code>lowerTerm</code> and
108 * <code>upperTerm</code>.
109 */
110 public RangeQuery(Term lowerTerm, Term upperTerm, boolean inclusive,
111 Collator collator)
112 {
113 this(lowerTerm, upperTerm, inclusive);
114 this.collator = collator;
115 }
116
117 public Query rewrite(IndexReader reader) throws IOException {
118
119 BooleanQuery query = new BooleanQuery(true);
120 String testField = getField();
121 if (collator != null) {
122 TermEnum enumerator = reader.terms(new Term(testField, ""));
123 String lowerTermText = lowerTerm != null ? lowerTerm.text() : null;
124 String upperTermText = upperTerm != null ? upperTerm.text() : null;
125
126 try {
127 do {
128 Term term = enumerator.term();
129 if (term != null && term.field() == testField) { // interned comparison
130 if ((lowerTermText == null
131 || (inclusive ? collator.compare(term.text(), lowerTermText) >= 0
132 : collator.compare(term.text(), lowerTermText) > 0))
133 && (upperTermText == null
134 || (inclusive ? collator.compare(term.text(), upperTermText) <= 0
135 : collator.compare(term.text(), upperTermText) < 0))) {
136 addTermToQuery(term, query);
137 }
138 }
139 }
140 while (enumerator.next());
141 }
142 finally {
143 enumerator.close();
144 }
145 }
146 else { // collator is null
147 TermEnum enumerator = reader.terms(lowerTerm);
148
149 try {
150
151 boolean checkLower = false;
152 if (!inclusive) // make adjustments to set to exclusive
153 checkLower = true;
154
155 do {
156 Term term = enumerator.term();
157 if (term != null && term.field() == testField) { // interned comparison
158 if (!checkLower || term.text().compareTo(lowerTerm.text()) > 0) {
159 checkLower = false;
160 if (upperTerm != null) {
161 int compare = upperTerm.text().compareTo(term.text());
162 /* if beyond the upper term, or is exclusive and
163 * this is equal to the upper term, break out */
164 if ((compare < 0) || (!inclusive && compare == 0))
165 break;
166 }
167 addTermToQuery(term, query); // Found a match
168 }
169 }
170 else {
171 break;
172 }
173 }
174 while (enumerator.next());
175 }
176 finally {
177 enumerator.close();
178 }
179 }
180 return query;
181 }
182
183 private void addTermToQuery(Term term, BooleanQuery query) {
184 TermQuery tq = new TermQuery(term);
185 tq.setBoost(getBoost()); // set the boost
186 query.add(tq, BooleanClause.Occur.SHOULD); // add to query
187 }
188
189 /** Returns the field name for this query */
190 public String getField() {
191 return (lowerTerm != null ? lowerTerm.field() : upperTerm.field());
192 }
193
194 /** Returns the lower term of this range query */
195 public Term getLowerTerm() { return lowerTerm; }
196
197 /** Returns the upper term of this range query */
198 public Term getUpperTerm() { return upperTerm; }
199
200 /** Returns <code>true</code> if the range query is inclusive */
201 public boolean isInclusive() { return inclusive; }
202
203 /** Returns the collator used to determine range inclusion, if any. */
204 public Collator getCollator() { return collator; }
205
206
207 /** Prints a user-readable version of this query. */
208 public String toString(String field)
209 {
210 StringBuffer buffer = new StringBuffer();
211 if (!getField().equals(field))
212 {
213 buffer.append(getField());
214 buffer.append(":");
215 }
216 buffer.append(inclusive ? "[" : "{");
217 buffer.append(lowerTerm != null ? lowerTerm.text() : "null");
218 buffer.append(" TO ");
219 buffer.append(upperTerm != null ? upperTerm.text() : "null");
220 buffer.append(inclusive ? "]" : "}");
221 buffer.append(ToStringUtils.boost(getBoost()));
222 return buffer.toString();
223 }
224
225 /** Returns true iff <code>o</code> is equal to this. */
226 public boolean equals(Object o) {
227 if (this == o) return true;
228 if (!(o instanceof RangeQuery)) return false;
229
230 final RangeQuery other = (RangeQuery) o;
231 if (this.getBoost() != other.getBoost()) return false;
232 if (this.inclusive != other.inclusive) return false;
233 if (this.collator != null && ! this.collator.equals(other.collator))
234 return false;
235
236 // one of lowerTerm and upperTerm can be null
237 if (this.lowerTerm != null ? !this.lowerTerm.equals(other.lowerTerm) : other.lowerTerm != null) return false;
238 if (this.upperTerm != null ? !this.upperTerm.equals(other.upperTerm) : other.upperTerm != null) return false;
239 return true;
240 }
241
242 /** Returns a hash code value for this object.*/
243 public int hashCode() {
244 int h = Float.floatToIntBits(getBoost());
245 h ^= lowerTerm != null ? lowerTerm.hashCode() : 0;
246 // reversible mix to make lower and upper position dependent and
247 // to prevent them from cancelling out.
248 h ^= (h << 25) | (h >>> 8);
249 h ^= upperTerm != null ? upperTerm.hashCode() : 0;
250 h ^= this.inclusive ? 0x2742E74A : 0;
251 h ^= collator != null ? collator.hashCode() : 0;
252 return h;
253 }
254 }