blob: 32703b24c9f2269efa4a2b9e0c045ad1a6883f21 [file] [log] [blame]
using Lucene.Net.Support;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using JCG = J2N.Collections.Generic;
namespace Lucene.Net.Search.Spans
{
/*
* 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 AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext;
using IBits = Lucene.Net.Util.IBits;
using Term = Lucene.Net.Index.Term;
using TermContext = Lucene.Net.Index.TermContext;
/// <summary>
/// Similar to <see cref="NearSpansOrdered"/>, but for the unordered case.
/// <para/>
/// Expert:
/// Only public for subclassing. Most implementations should not need this class
/// </summary>
public class NearSpansUnordered : Spans
{
private SpanNearQuery query;
private IList<SpansCell> ordered = new List<SpansCell>(); // spans in query order
private Spans[] subSpans;
private int slop; // from query
private SpansCell first; // linked list of spans
private SpansCell last; // sorted by doc only
private int totalLength; // sum of current lengths
private CellQueue queue; // sorted queue of spans
private SpansCell max; // max element in queue
private bool more = true; // true iff not done
private bool firstTime = true; // true before first next()
private class CellQueue : Util.PriorityQueue<SpansCell>
{
private readonly NearSpansUnordered outerInstance;
public CellQueue(NearSpansUnordered outerInstance, int size)
: base(size)
{
this.outerInstance = outerInstance;
}
protected internal override bool LessThan(SpansCell spans1, SpansCell spans2)
{
if (spans1.Doc == spans2.Doc)
{
return NearSpansOrdered.DocSpansOrdered(spans1, spans2);
}
else
{
return spans1.Doc < spans2.Doc;
}
}
}
/// <summary>
/// Wraps a <see cref="Spans"/>, and can be used to form a linked list. </summary>
private class SpansCell : Spans
{
private readonly NearSpansUnordered outerInstance;
internal Spans spans;
internal SpansCell next;
private int length = -1;
private int index;
public SpansCell(NearSpansUnordered outerInstance, Spans spans, int index)
{
this.outerInstance = outerInstance;
this.spans = spans;
this.index = index;
}
public override bool Next()
{
return Adjust(spans.Next());
}
public override bool SkipTo(int target)
{
return Adjust(spans.SkipTo(target));
}
private bool Adjust(bool condition)
{
if (length != -1)
{
outerInstance.totalLength -= length; // subtract old length
}
if (condition)
{
length = End - Start;
outerInstance.totalLength += length; // add new length
if (outerInstance.max == null || Doc > outerInstance.max.Doc || (Doc == outerInstance.max.Doc) && (End > outerInstance.max.End))
{
outerInstance.max = this;
}
}
outerInstance.more = condition;
return condition;
}
public override int Doc
{
get { return spans.Doc; }
}
public override int Start
{
get { return spans.Start; }
}
// TODO: Remove warning after API has been finalized
public override int End
{
get { return spans.End; }
}
public override ICollection<byte[]> GetPayload()
{
return new List<byte[]>(spans.GetPayload());
}
// TODO: Remove warning after API has been finalized
public override bool IsPayloadAvailable
{
get
{
return spans.IsPayloadAvailable;
}
}
public override long GetCost()
{
return spans.GetCost();
}
public override string ToString()
{
return spans.ToString() + "#" + index;
}
}
public NearSpansUnordered(SpanNearQuery query, AtomicReaderContext context, IBits acceptDocs, IDictionary<Term, TermContext> termContexts)
{
this.query = query;
this.slop = query.Slop;
SpanQuery[] clauses = query.GetClauses();
queue = new CellQueue(this, clauses.Length);
subSpans = new Spans[clauses.Length];
for (int i = 0; i < clauses.Length; i++)
{
SpansCell cell = new SpansCell(this, clauses[i].GetSpans(context, acceptDocs, termContexts), i);
ordered.Add(cell);
subSpans[i] = cell.spans;
}
}
[WritableArray]
[SuppressMessage("Microsoft.Performance", "CA1819", Justification = "Lucene's design requires some writable array properties")]
public virtual Spans[] SubSpans
{
get { return subSpans; }
}
public override bool Next()
{
if (firstTime)
{
InitList(true);
ListToQueue(); // initialize queue
firstTime = false;
}
else if (more)
{
if (Min.Next()) // trigger further scanning
{
queue.UpdateTop(); // maintain queue
}
else
{
more = false;
}
}
while (more)
{
bool queueStale = false;
if (Min.Doc != max.Doc) // maintain list
{
QueueToList();
queueStale = true;
}
// skip to doc w/ all clauses
while (more && first.Doc < last.Doc)
{
more = first.SkipTo(last.Doc); // skip first upto last
FirstToLast(); // and move it to the end
queueStale = true;
}
if (!more)
{
return false;
}
// found doc w/ all clauses
if (queueStale) // maintain the queue
{
ListToQueue();
queueStale = false;
}
if (AtMatch)
{
return true;
}
more = Min.Next();
if (more)
{
queue.UpdateTop(); // maintain queue
}
}
return false; // no more matches
}
public override bool SkipTo(int target)
{
if (firstTime) // initialize
{
InitList(false);
for (SpansCell cell = first; more && cell != null; cell = cell.next)
{
more = cell.SkipTo(target); // skip all
}
if (more)
{
ListToQueue();
}
firstTime = false;
} // normal case
else
{
while (more && Min.Doc < target) // skip as needed
{
if (Min.SkipTo(target))
{
queue.UpdateTop();
}
else
{
more = false;
}
}
}
return more && (AtMatch || Next());
}
private SpansCell Min
{
get { return queue.Top; }
}
public override int Doc
{
get { return Min.Doc; }
}
public override int Start
{
get { return Min.Start; }
}
public override int End
{
get { return max.End; }
}
// TODO: Remove warning after API has been finalized
/// <summary>
/// WARNING: The List is not necessarily in order of the the positions </summary>
/// <returns> Collection of <see cref="T:byte[]"/> payloads </returns>
/// <exception cref="System.IO.IOException"> if there is a low-level I/O error </exception>
public override ICollection<byte[]> GetPayload()
{
var matchPayload = new JCG.HashSet<byte[]>();
for (var cell = first; cell != null; cell = cell.next)
{
if (cell.IsPayloadAvailable)
{
matchPayload.UnionWith(cell.GetPayload());
}
}
return matchPayload;
}
// TODO: Remove warning after API has been finalized
public override bool IsPayloadAvailable
{
get
{
SpansCell pointer = Min;
while (pointer != null)
{
if (pointer.IsPayloadAvailable)
{
return true;
}
pointer = pointer.next;
}
return false;
}
}
public override long GetCost()
{
long minCost = long.MaxValue;
for (int i = 0; i < subSpans.Length; i++)
{
minCost = Math.Min(minCost, subSpans[i].GetCost());
}
return minCost;
}
public override string ToString()
{
return this.GetType().Name + "(" + query.ToString() + ")@" + (firstTime ? "START" : (more ? (Doc + ":" + Start + "-" + End) : "END"));
}
private void InitList(bool next)
{
for (int i = 0; more && i < ordered.Count; i++)
{
SpansCell cell = ordered[i];
if (next)
{
more = cell.Next(); // move to first entry
}
if (more)
{
AddToList(cell); // add to list
}
}
}
private void AddToList(SpansCell cell)
{
if (last != null) // add next to end of list
{
last.next = cell;
}
else
{
first = cell;
}
last = cell;
cell.next = null;
}
private void FirstToLast()
{
last.next = first; // move first to end of list
last = first;
first = first.next;
last.next = null;
}
private void QueueToList()
{
last = first = null;
while (queue.Top != null)
{
AddToList(queue.Pop());
}
}
private void ListToQueue()
{
queue.Clear(); // rebuild queue
for (SpansCell cell = first; cell != null; cell = cell.next)
{
queue.Add(cell); // add to queue from list
}
}
private bool AtMatch
{
get { return (Min.Doc == max.Doc) && ((max.End - Min.Start - totalLength) <= slop); }
}
}
}