/*
* Copyright 2004 The Apache Software Foundation
*
* Licensed 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 DocumentLucene.Net.Documents.Document;
namespace Lucene.Net.Search{
/// <summary>A ranked list of documents, used to hold search results. </summary>
public sealed class Hits
{
private Weight weight;
private Searcher searcher;
private Filter filter = null;
private Sort sort = null;
private int length; // the total number of hits
private System.Collections.ArrayList hitDocs = System.Collections.ArrayList.Synchronized(new System.Collections.ArrayList(10)); // cache of hits retrieved
private HitDoc first; // head of LRU cache
private HitDoc last; // tail of LRU cache
private int numDocs = 0; // number cached
private int maxDocs = 200; // max to cache
internal Hits(Searcher s, Query q, Filter f)
{
weight = q.Weight(s);
searcher = s;
filter = f;
GetMoreDocs(50); // retrieve 100 initially
}
internal Hits(Searcher s, Query q, Filter f, Sort o)
{
weight = q.Weight(s);
searcher = s;
filter = f;
sort = o;
GetMoreDocs(50); // retrieve 100 initially
}
/// <summary> Tries to add new documents to hitDocs.
/// Ensures that the hit numbered <code>min</code> has been retrieved.
/// </summary>
private void GetMoreDocs(int min)
{
if (hitDocs.Count > min)
{
min = hitDocs.Count;
}
int n = min * 2; // double # retrieved
TopDocs topDocs = (sort == null) ? searcher.Search(weight, filter, n) : searcher.Search(weight, filter, n, sort);
length = topDocs.totalHits;
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
float scoreNorm = 1.0f;
if (length > 0 && topDocs.GetMaxScore() > 1.0f)
{
scoreNorm = 1.0f / topDocs.GetMaxScore();
}
int end = scoreDocs.Length < length?scoreDocs.Length:length;
for (int i = hitDocs.Count; i < end; i++)
{
hitDocs.Add(new HitDoc(scoreDocs[i].score * scoreNorm, scoreDocs[i].doc));
}
}
/// <summary>Returns the total number of hits available in this set. </summary>
public int Length()
{
return length;
}
/// <summary>Returns the stored fields of the n<sup>th</sup> document in this set.
/// <p>Documents are cached, so that repeated requests for the same element may
/// return the same Document object.
/// </summary>
public Document Doc(int n)
{
HitDoc hitDoc = HitDoc(n);
// Update LRU cache of documents
Remove(hitDoc); // remove from list, if there
AddToFront(hitDoc); // add to front of list
if (numDocs > maxDocs)
{
// if cache is full
HitDoc oldLast = last;
Remove(last); // flush last
oldLast.doc = null; // let doc get gc'd
}
if (hitDoc.doc == null)
{
hitDoc.doc = searcher.Doc(hitDoc.id); // cache miss: read document
}
return hitDoc.doc;
}
/// <summary>Returns the score for the nth document in this set. </summary>
public float Score(int n)
{
return HitDoc(n).score;
}
/// <summary>Returns the id for the nth document in this set. </summary>
public int Id(int n)
{
return HitDoc(n).id;
}
/// <summary> Returns a {@link HitIterator} to navigate the Hits. Each item returned
/// from {@link Iterator#next()} is a {@link Hit}.
/// <p>
/// <b>Caution:</b> Iterate only over the hits needed. Iterating over all
/// hits is generally not desirable and may be the source of
/// performance issues.
/// </p>
/// </summary>
public System.Collections.IEnumerator Iterator()
{
return new HitIterator(this);
}
private HitDoc HitDoc(int n)
{
if (n >= length)
{
throw new System.IndexOutOfRangeException("Not a valid hit number: " + n);
}
if (n >= hitDocs.Count)
{
GetMoreDocs(n);
}
return (HitDoc) hitDocs[n];
}
private void AddToFront(HitDoc hitDoc)
{
// insert at front of cache
if (first == null)
{
last = hitDoc;
}
else
{
first.prev = hitDoc;
}
hitDoc.next = first;
first = hitDoc;
hitDoc.prev = null;
numDocs++;
}
private void Remove(HitDoc hitDoc)
{
// remove from cache
if (hitDoc.doc == null)
{
// it's not in the list
return ; // abort
}
if (hitDoc.next == null)
{
last = hitDoc.prev;
}
else
{
hitDoc.next.prev = hitDoc.prev;
}
if (hitDoc.prev == null)
{
first = hitDoc.next;
}
else
{
hitDoc.prev.next = hitDoc.next;
}
numDocs--;
}
}
sealed class HitDoc
{
internal float score;
internal int id;
internal Document doc = null;
internal HitDoc next; // in doubly-linked cache
internal HitDoc prev; // in doubly-linked cache
internal HitDoc(float s, int i)
{
score = s;
id = i;
}
}
}
|