1 package org.apache.lucene.search.payloads;
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 org.apache.lucene.index.Term;
21 import org.apache.lucene.index.IndexReader;
22 import org.apache.lucene.index.TermPositions;
23 import org.apache.lucene.search.Searcher;
24 import org.apache.lucene.search.Scorer;
25 import org.apache.lucene.search.Weight;
26 import org.apache.lucene.search.Similarity;
27 import org.apache.lucene.search.Explanation;
28 import org.apache.lucene.search.ComplexExplanation;
29 import org.apache.lucene.search.spans.TermSpans;
30 import org.apache.lucene.search.spans.SpanTermQuery;
31 import org.apache.lucene.search.spans.SpanWeight;
32 import org.apache.lucene.search.spans.SpanScorer;
34 import java.io.IOException;
37 * This class is very similar to
38 * {@link org.apache.lucene.search.spans.SpanTermQuery} except that it factors
39 * in the value of the payload located at each of the positions where the
40 * {@link org.apache.lucene.index.Term} occurs.
42 * In order to take advantage of this, you must override
43 * {@link org.apache.lucene.search.Similarity#scorePayload(int, String, int, int, byte[],int,int)}
44 * which returns 1 by default.
46 * Payload scores are aggregated using a pluggable {@link PayloadFunction}.
48 public class PayloadTermQuery extends SpanTermQuery {
49 protected PayloadFunction function;
50 private boolean includeSpanScore;
52 public PayloadTermQuery(Term term, PayloadFunction function) {
53 this(term, function, true);
56 public PayloadTermQuery(Term term, PayloadFunction function,
57 boolean includeSpanScore) {
59 this.function = function;
60 this.includeSpanScore = includeSpanScore;
64 public Weight createWeight(Searcher searcher) throws IOException {
65 return new PayloadTermWeight(this, searcher);
68 protected class PayloadTermWeight extends SpanWeight {
70 public PayloadTermWeight(PayloadTermQuery query, Searcher searcher)
72 super(query, searcher);
76 public Scorer scorer(IndexReader reader, boolean scoreDocsInOrder,
77 boolean topScorer) throws IOException {
78 return new PayloadTermSpanScorer((TermSpans) query.getSpans(reader),
79 this, similarity, reader.norms(query.getField()));
82 protected class PayloadTermSpanScorer extends SpanScorer {
83 // TODO: is this the best way to allocate this?
84 protected byte[] payload = new byte[256];
85 protected TermPositions positions;
86 protected float payloadScore;
87 protected int payloadsSeen;
89 public PayloadTermSpanScorer(TermSpans spans, Weight weight,
90 Similarity similarity, byte[] norms) throws IOException {
91 super(spans, weight, similarity, norms);
92 positions = spans.getPositions();
96 protected boolean setFreqCurrentDoc() throws IOException {
104 Similarity similarity1 = getSimilarity();
105 while (more && doc == spans.doc()) {
106 int matchLength = spans.end() - spans.start();
108 freq += similarity1.sloppyFreq(matchLength);
109 processPayload(similarity1);
111 more = spans.next();// this moves positions to the next match in this
114 return more || (freq != 0);
117 protected void processPayload(Similarity similarity) throws IOException {
118 if (positions.isPayloadAvailable()) {
119 payload = positions.getPayload(payload, 0);
120 payloadScore = function.currentScore(doc, term.field(),
121 spans.start(), spans.end(), payloadsSeen, payloadScore,
122 similarity.scorePayload(doc, term.field(), spans.start(), spans
123 .end(), payload, 0, positions.getPayloadLength()));
127 // zero out the payload?
133 * @return {@link #getSpanScore()} * {@link #getPayloadScore()}
134 * @throws IOException
137 public float score() throws IOException {
139 return includeSpanScore ? getSpanScore() * getPayloadScore()
144 * Returns the SpanScorer score only.
146 * Should not be overridden without good cause!
148 * @return the score for just the Span part w/o the payload
149 * @throws IOException
153 protected float getSpanScore() throws IOException {
154 return super.score();
158 * The score for the payload
160 * @return The score, as calculated by
161 * {@link PayloadFunction#docScore(int, String, int, float)}
163 protected float getPayloadScore() {
164 return function.docScore(doc, term.field(), payloadsSeen, payloadScore);
168 protected Explanation explain(final int doc) throws IOException {
169 ComplexExplanation result = new ComplexExplanation();
170 Explanation nonPayloadExpl = super.explain(doc);
171 result.addDetail(nonPayloadExpl);
172 // QUESTION: Is there a way to avoid this skipTo call? We need to know
173 // whether to load the payload or not
174 Explanation payloadBoost = new Explanation();
175 result.addDetail(payloadBoost);
177 float payloadScore = getPayloadScore();
178 payloadBoost.setValue(payloadScore);
179 // GSI: I suppose we could toString the payload, but I don't think that
180 // would be a good idea
181 payloadBoost.setDescription("scorePayload(...)");
182 result.setValue(nonPayloadExpl.getValue() * payloadScore);
183 result.setDescription("btq, product of:");
184 result.setMatch(nonPayloadExpl.getValue() == 0 ? Boolean.FALSE
185 : Boolean.TRUE); // LUCENE-1303
193 public int hashCode() {
194 final int prime = 31;
195 int result = super.hashCode();
196 result = prime * result + ((function == null) ? 0 : function.hashCode());
197 result = prime * result + (includeSpanScore ? 1231 : 1237);
202 public boolean equals(Object obj) {
205 if (!super.equals(obj))
207 if (getClass() != obj.getClass())
209 PayloadTermQuery other = (PayloadTermQuery) obj;
210 if (function == null) {
211 if (other.function != null)
213 } else if (!function.equals(other.function))
215 if (includeSpanScore != other.includeSpanScore)