/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using IndexReader = Lucene.Net.Index.IndexReader;
using Term = Lucene.Net.Index.Term;
using TermPositions = Lucene.Net.Index.TermPositions;
using Lucene.Net.Search;
using Searchable = Lucene.Net.Search.Searchable;
using SpanScorer = Lucene.Net.Search.Spans.SpanScorer;
using SpanTermQuery = Lucene.Net.Search.Spans.SpanTermQuery;
using SpanWeight = Lucene.Net.Search.Spans.SpanWeight;
using TermSpans = Lucene.Net.Search.Spans.TermSpans;
namespace Lucene.Net.Search.Payloads
{
/// The BoostingTermQuery is very similar to the {@link Lucene.Net.Search.Spans.SpanTermQuery} except
/// that it factors in the value of the payload located at each of the positions where the
/// {@link Lucene.Net.Index.Term} occurs.
///
/// In order to take advantage of this, you must override {@link Lucene.Net.Search.Similarity#ScorePayload(String, byte[],int,int)}
/// which returns 1 by default.
///
/// Payload scores are averaged across term occurrences in the document.
///
///
///
///
[Serializable]
public class BoostingTermQuery : SpanTermQuery
{
public BoostingTermQuery(Term term) : base(term)
{
}
protected internal override Weight CreateWeight(Searcher searcher)
{
return new BoostingTermWeight(this, this, searcher);
}
[Serializable]
protected internal class BoostingTermWeight : SpanWeight, Weight
{
private void InitBlock(BoostingTermQuery enclosingInstance)
{
this.enclosingInstance = enclosingInstance;
}
private BoostingTermQuery enclosingInstance;
public BoostingTermQuery Enclosing_Instance
{
get
{
return enclosingInstance;
}
}
public BoostingTermWeight(BoostingTermQuery enclosingInstance, BoostingTermQuery query, Searcher searcher) : base(query, searcher)
{
InitBlock(enclosingInstance);
}
public override Scorer Scorer(IndexReader reader)
{
return new BoostingSpanScorer(this, (TermSpans) query.GetSpans(reader), this, similarity, reader.Norms(query.GetField()));
}
internal class BoostingSpanScorer : SpanScorer
{
private void InitBlock(BoostingTermWeight enclosingInstance)
{
this.enclosingInstance = enclosingInstance;
}
private BoostingTermWeight enclosingInstance;
public BoostingTermWeight Enclosing_Instance
{
get
{
return enclosingInstance;
}
}
//TODO: is this the best way to allocate this?
internal byte[] payload = new byte[256];
private TermPositions positions;
protected internal float payloadScore;
private int payloadsSeen;
public BoostingSpanScorer(BoostingTermWeight enclosingInstance, TermSpans spans, Weight weight, Similarity similarity, byte[] norms) : base(spans, weight, similarity, norms)
{
InitBlock(enclosingInstance);
positions = spans.GetPositions();
}
protected internal override bool SetFreqCurrentDoc()
{
if (!more)
{
return false;
}
doc = spans.Doc();
freq = 0.0f;
payloadScore = 0;
payloadsSeen = 0;
Similarity similarity1 = GetSimilarity();
while (more && doc == spans.Doc())
{
int matchLength = spans.End() - spans.Start();
freq += similarity1.SloppyFreq(matchLength);
ProcessPayload(similarity1);
more = spans.Next(); //this moves positions to the next match in this document
}
return more || (freq != 0);
}
protected internal virtual void ProcessPayload(Similarity similarity)
{
if (positions.IsPayloadAvailable())
{
payload = positions.GetPayload(payload, 0);
payloadScore += similarity.ScorePayload(Enclosing_Instance.Enclosing_Instance.term.Field(), payload, 0, positions.GetPayloadLength());
payloadsSeen++;
}
else
{
//zero out the payload?
}
}
public override float Score()
{
return base.Score() * (payloadsSeen > 0 ? (payloadScore / payloadsSeen) : 1);
}
public override Explanation Explain(int doc)
{
Explanation result = new Explanation();
Explanation nonPayloadExpl = base.Explain(doc);
result.AddDetail(nonPayloadExpl);
//QUESTION: Is there a wau to avoid this skipTo call? We need to know whether to load the payload or not
Explanation payloadBoost = new Explanation();
result.AddDetail(payloadBoost);
/*
if (skipTo(doc) == true) {
processPayload();
}*/
float avgPayloadScore = (payloadsSeen > 0 ? (payloadScore / payloadsSeen) : 1);
payloadBoost.SetValue(avgPayloadScore);
//GSI: I suppose we could toString the payload, but I don't think that would be a good idea
payloadBoost.SetDescription("scorePayload(...)");
result.SetValue(nonPayloadExpl.GetValue() * avgPayloadScore);
result.SetDescription("btq, product of:");
return result;
}
}
}
public override bool Equals(System.Object o)
{
if (!(o is BoostingTermQuery))
return false;
BoostingTermQuery other = (BoostingTermQuery) o;
return (this.GetBoost() == other.GetBoost()) && this.term.Equals(other.term);
}
public override int GetHashCode() // {{Aroush-2.3.1}} Do we need this methods?
{
return base.GetHashCode();
}
}
}