1 package org.apache.lucene.search.function;
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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.
20 import java.io.IOException;
22 import java.util.Arrays;
24 import org.apache.lucene.index.IndexReader;
25 import org.apache.lucene.index.Term;
26 import org.apache.lucene.search.ComplexExplanation;
27 import org.apache.lucene.search.Explanation;
28 import org.apache.lucene.search.Query;
29 import org.apache.lucene.search.Weight;
30 import org.apache.lucene.search.Scorer;
31 import org.apache.lucene.search.Searcher;
32 import org.apache.lucene.search.Similarity;
33 import org.apache.lucene.util.ToStringUtils;
36 * Query that sets document score as a programmatic function of several (sub) scores:
38 * <li>the score of its subQuery (any query)</li>
39 * <li>(optional) the score of its ValueSourceQuery (or queries).
40 * For most simple/convenient use cases this query is likely to be a
41 * {@link org.apache.lucene.search.function.FieldScoreQuery FieldScoreQuery}</li>
43 * Subclasses can modify the computation by overriding {@link #getCustomScoreProvider}.
45 * @lucene.experimental
47 public class CustomScoreQuery extends Query {
49 private Query subQuery;
50 private ValueSourceQuery[] valSrcQueries; // never null (empty array if there are no valSrcQueries).
51 private boolean strict = false; // if true, valueSource part of query does not take part in weights normalization.
54 * Create a CustomScoreQuery over input subQuery.
55 * @param subQuery the sub query whose scored is being customized. Must not be null.
57 public CustomScoreQuery(Query subQuery) {
58 this(subQuery, new ValueSourceQuery[0]);
62 * Create a CustomScoreQuery over input subQuery and a {@link ValueSourceQuery}.
63 * @param subQuery the sub query whose score is being customized. Must not be null.
64 * @param valSrcQuery a value source query whose scores are used in the custom score
65 * computation. For most simple/convenient use case this would be a
66 * {@link org.apache.lucene.search.function.FieldScoreQuery FieldScoreQuery}.
67 * This parameter is optional - it can be null.
69 public CustomScoreQuery(Query subQuery, ValueSourceQuery valSrcQuery) {
70 this(subQuery, valSrcQuery!=null ? // don't want an array that contains a single null..
71 new ValueSourceQuery[] {valSrcQuery} : new ValueSourceQuery[0]);
75 * Create a CustomScoreQuery over input subQuery and a {@link ValueSourceQuery}.
76 * @param subQuery the sub query whose score is being customized. Must not be null.
77 * @param valSrcQueries value source queries whose scores are used in the custom score
78 * computation. For most simple/convenient use case these would be
79 * {@link org.apache.lucene.search.function.FieldScoreQuery FieldScoreQueries}.
80 * This parameter is optional - it can be null or even an empty array.
82 public CustomScoreQuery(Query subQuery, ValueSourceQuery... valSrcQueries) {
83 this.subQuery = subQuery;
84 this.valSrcQueries = valSrcQueries!=null?
85 valSrcQueries : new ValueSourceQuery[0];
86 if (subQuery == null) throw new IllegalArgumentException("<subquery> must not be null!");
89 /*(non-Javadoc) @see org.apache.lucene.search.Query#rewrite(org.apache.lucene.index.IndexReader) */
91 public Query rewrite(IndexReader reader) throws IOException {
92 CustomScoreQuery clone = null;
94 final Query sq = subQuery.rewrite(reader);
96 clone = (CustomScoreQuery) clone();
100 for(int i = 0; i < valSrcQueries.length; i++) {
101 final ValueSourceQuery v = (ValueSourceQuery) valSrcQueries[i].rewrite(reader);
102 if (v != valSrcQueries[i]) {
103 if (clone == null) clone = (CustomScoreQuery) clone();
104 clone.valSrcQueries[i] = v;
108 return (clone == null) ? this : clone;
111 /*(non-Javadoc) @see org.apache.lucene.search.Query#extractTerms(java.util.Set) */
113 public void extractTerms(Set<Term> terms) {
114 subQuery.extractTerms(terms);
115 for(int i = 0; i < valSrcQueries.length; i++) {
116 valSrcQueries[i].extractTerms(terms);
120 /*(non-Javadoc) @see org.apache.lucene.search.Query#clone() */
122 public Object clone() {
123 CustomScoreQuery clone = (CustomScoreQuery)super.clone();
124 clone.subQuery = (Query) subQuery.clone();
125 clone.valSrcQueries = new ValueSourceQuery[valSrcQueries.length];
126 for(int i = 0; i < valSrcQueries.length; i++) {
127 clone.valSrcQueries[i] = (ValueSourceQuery) valSrcQueries[i].clone();
132 /* (non-Javadoc) @see org.apache.lucene.search.Query#toString(java.lang.String) */
134 public String toString(String field) {
135 StringBuilder sb = new StringBuilder(name()).append("(");
136 sb.append(subQuery.toString(field));
137 for(int i = 0; i < valSrcQueries.length; i++) {
138 sb.append(", ").append(valSrcQueries[i].toString(field));
141 sb.append(strict?" STRICT" : "");
142 return sb.toString() + ToStringUtils.boost(getBoost());
145 /** Returns true if <code>o</code> is equal to this. */
147 public boolean equals(Object o) {
150 if (!super.equals(o))
152 if (getClass() != o.getClass()) {
155 CustomScoreQuery other = (CustomScoreQuery)o;
156 if (this.getBoost() != other.getBoost() ||
157 !this.subQuery.equals(other.subQuery) ||
158 this.strict != other.strict ||
159 this.valSrcQueries.length != other.valSrcQueries.length) {
162 return Arrays.equals(valSrcQueries, other.valSrcQueries);
165 /** Returns a hash code value for this object. */
167 public int hashCode() {
168 return (getClass().hashCode() + subQuery.hashCode() + Arrays.hashCode(valSrcQueries))
169 ^ Float.floatToIntBits(getBoost()) ^ (strict ? 1234 : 4321);
173 * Returns a {@link CustomScoreProvider} that calculates the custom scores
174 * for the given {@link IndexReader}. The default implementation returns a default
175 * implementation as specified in the docs of {@link CustomScoreProvider}.
178 protected CustomScoreProvider getCustomScoreProvider(IndexReader reader) throws IOException {
179 return new CustomScoreProvider(reader);
182 //=========================== W E I G H T ============================
184 private class CustomWeight extends Weight {
185 Similarity similarity;
186 Weight subQueryWeight;
187 Weight[] valSrcWeights;
190 public CustomWeight(Searcher searcher) throws IOException {
191 this.similarity = getSimilarity(searcher);
192 this.subQueryWeight = subQuery.createWeight(searcher);
193 this.valSrcWeights = new Weight[valSrcQueries.length];
194 for(int i = 0; i < valSrcQueries.length; i++) {
195 this.valSrcWeights[i] = valSrcQueries[i].createWeight(searcher);
197 this.qStrict = strict;
200 /*(non-Javadoc) @see org.apache.lucene.search.Weight#getQuery() */
202 public Query getQuery() {
203 return CustomScoreQuery.this;
206 /*(non-Javadoc) @see org.apache.lucene.search.Weight#getValue() */
208 public float getValue() {
212 /*(non-Javadoc) @see org.apache.lucene.search.Weight#sumOfSquaredWeights() */
214 public float sumOfSquaredWeights() throws IOException {
215 float sum = subQueryWeight.sumOfSquaredWeights();
216 for(int i = 0; i < valSrcWeights.length; i++) {
218 valSrcWeights[i].sumOfSquaredWeights(); // do not include ValueSource part in the query normalization
220 sum += valSrcWeights[i].sumOfSquaredWeights();
223 sum *= getBoost() * getBoost(); // boost each sub-weight
227 /*(non-Javadoc) @see org.apache.lucene.search.Weight#normalize(float) */
229 public void normalize(float norm) {
230 norm *= getBoost(); // incorporate boost
231 subQueryWeight.normalize(norm);
232 for(int i = 0; i < valSrcWeights.length; i++) {
234 valSrcWeights[i].normalize(1); // do not normalize the ValueSource part
236 valSrcWeights[i].normalize(norm);
242 public Scorer scorer(IndexReader reader, boolean scoreDocsInOrder, boolean topScorer) throws IOException {
243 // Pass true for "scoresDocsInOrder", because we
244 // require in-order scoring, even if caller does not,
245 // since we call advance on the valSrcScorers. Pass
246 // false for "topScorer" because we will not invoke
247 // score(Collector) on these scorers:
248 Scorer subQueryScorer = subQueryWeight.scorer(reader, true, false);
249 if (subQueryScorer == null) {
252 Scorer[] valSrcScorers = new Scorer[valSrcWeights.length];
253 for(int i = 0; i < valSrcScorers.length; i++) {
254 valSrcScorers[i] = valSrcWeights[i].scorer(reader, true, topScorer);
256 return new CustomScorer(similarity, reader, this, subQueryScorer, valSrcScorers);
260 public Explanation explain(IndexReader reader, int doc) throws IOException {
261 Explanation explain = doExplain(reader, doc);
262 return explain == null ? new Explanation(0.0f, "no matching docs") : explain;
265 private Explanation doExplain(IndexReader reader, int doc) throws IOException {
266 Explanation subQueryExpl = subQueryWeight.explain(reader, doc);
267 if (!subQueryExpl.isMatch()) {
271 Explanation[] valSrcExpls = new Explanation[valSrcWeights.length];
272 for(int i = 0; i < valSrcWeights.length; i++) {
273 valSrcExpls[i] = valSrcWeights[i].explain(reader, doc);
275 Explanation customExp = CustomScoreQuery.this.getCustomScoreProvider(reader).customExplain(doc,subQueryExpl,valSrcExpls);
276 float sc = getValue() * customExp.getValue();
277 Explanation res = new ComplexExplanation(
278 true, sc, CustomScoreQuery.this.toString() + ", product of:");
279 res.addDetail(customExp);
280 res.addDetail(new Explanation(getValue(), "queryBoost")); // actually using the q boost as q weight (== weight value)
285 public boolean scoresDocsOutOfOrder() {
292 //=========================== S C O R E R ============================
295 * A scorer that applies a (callback) function on scores of the subQuery.
297 private class CustomScorer extends Scorer {
298 private final float qWeight;
299 private Scorer subQueryScorer;
300 private Scorer[] valSrcScorers;
301 private final CustomScoreProvider provider;
302 private float vScores[]; // reused in score() to avoid allocating this array for each doc
305 private CustomScorer(Similarity similarity, IndexReader reader, CustomWeight w,
306 Scorer subQueryScorer, Scorer[] valSrcScorers) throws IOException {
308 this.qWeight = w.getValue();
309 this.subQueryScorer = subQueryScorer;
310 this.valSrcScorers = valSrcScorers;
311 this.vScores = new float[valSrcScorers.length];
312 this.provider = CustomScoreQuery.this.getCustomScoreProvider(reader);
316 public int nextDoc() throws IOException {
317 int doc = subQueryScorer.nextDoc();
318 if (doc != NO_MORE_DOCS) {
319 for (int i = 0; i < valSrcScorers.length; i++) {
320 valSrcScorers[i].advance(doc);
328 return subQueryScorer.docID();
331 /*(non-Javadoc) @see org.apache.lucene.search.Scorer#score() */
333 public float score() throws IOException {
334 for (int i = 0; i < valSrcScorers.length; i++) {
335 vScores[i] = valSrcScorers[i].score();
337 return qWeight * provider.customScore(subQueryScorer.docID(), subQueryScorer.score(), vScores);
341 public int advance(int target) throws IOException {
342 int doc = subQueryScorer.advance(target);
343 if (doc != NO_MORE_DOCS) {
344 for (int i = 0; i < valSrcScorers.length; i++) {
345 valSrcScorers[i].advance(doc);
353 public Weight createWeight(Searcher searcher) throws IOException {
354 return new CustomWeight(searcher);
358 * Checks if this is strict custom scoring.
359 * In strict custom scoring, the ValueSource part does not participate in weight normalization.
360 * This may be useful when one wants full control over how scores are modified, and does
361 * not care about normalizing by the ValueSource part.
362 * One particular case where this is useful if for testing this query.
364 * Note: only has effect when the ValueSource part is not null.
366 public boolean isStrict() {
371 * Set the strict mode of this query.
372 * @param strict The strict mode to set.
375 public void setStrict(boolean strict) {
376 this.strict = strict;
380 * A short name of this query, used in {@link #toString(String)}.
382 public String name() {