blob: f2f201f0fcc925a15eb6df3cf1da6ab9932034b8 [file] [log] [blame]
using Lucene.Net.Diagnostics;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace Lucene.Net.Search
{
/*
* 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 BytesRef = Lucene.Net.Util.BytesRef;
using StringHelper = Lucene.Net.Util.StringHelper;
/// <summary>
/// Stores information about how to sort documents by terms in an individual
/// field. Fields must be indexed in order to sort by them.
///
/// <para/>Created: Feb 11, 2004 1:25:29 PM
/// <para/>
/// @since lucene 1.4 </summary>
/// <seealso cref="Sort"/>
public class SortField
{
// LUCENENET NOTE: de-nested the Type enum and renamed to SortFieldType to avoid potential naming collisions with System.Type
/// <summary>
/// Represents sorting by document score (relevance). </summary>
public static readonly SortField FIELD_SCORE = new SortField(null, SortFieldType.SCORE);
/// <summary>
/// Represents sorting by document number (index order). </summary>
public static readonly SortField FIELD_DOC = new SortField(null, SortFieldType.DOC);
private string field;
private SortFieldType type; // defaults to determining type dynamically
internal bool reverse = false; // defaults to natural order
private readonly FieldCache.IParser parser; // LUCENENET: marked readonly
// Used for CUSTOM sort
private readonly FieldComparerSource comparerSource; // LUCENENET: marked readonly
// Used for 'sortMissingFirst/Last'
public virtual object MissingValue
{
get => m_missingValue;
set
{
if (type == SortFieldType.STRING)
{
if (value != STRING_FIRST && value != STRING_LAST)
{
throw new ArgumentException("For STRING type, missing value must be either STRING_FIRST or STRING_LAST");
}
}
#pragma warning disable 612, 618
else if (type != SortFieldType.BYTE && type != SortFieldType.INT16
#pragma warning restore 612, 618
&& type != SortFieldType.INT32 && type != SortFieldType.SINGLE && type != SortFieldType.INT64 && type != SortFieldType.DOUBLE)
{
throw new ArgumentException("Missing value only works for numeric or STRING types");
}
this.m_missingValue = value;
}
}
protected object m_missingValue = null; // LUCENENET NOTE: added protected backing field
/// <summary>
/// Creates a sort by terms in the given field with the type of term
/// values explicitly given. </summary>
/// <param name="field"> Name of field to sort by. Can be <c>null</c> if
/// <paramref name="type"/> is <see cref="SortFieldType.SCORE"/> or <see cref="SortFieldType.DOC"/>. </param>
/// <param name="type"> Type of values in the terms. </param>
public SortField(string field, SortFieldType type)
{
InitFieldType(field, type);
}
/// <summary>
/// Creates a sort, possibly in reverse, by terms in the given field with the
/// type of term values explicitly given. </summary>
/// <param name="field"> Name of field to sort by. Can be <c>null</c> if
/// <paramref name="type"/> is <see cref="SortFieldType.SCORE"/> or <see cref="SortFieldType.DOC"/>. </param>
/// <param name="type"> Type of values in the terms. </param>
/// <param name="reverse"> <c>True</c> if natural order should be reversed. </param>
public SortField(string field, SortFieldType type, bool reverse)
{
InitFieldType(field, type);
this.reverse = reverse;
}
/// <summary>
/// Creates a sort by terms in the given field, parsed
/// to numeric values using a custom <see cref="FieldCache.IParser"/>. </summary>
/// <param name="field"> Name of field to sort by. Must not be <c>null</c>. </param>
/// <param name="parser"> Instance of a <see cref="FieldCache.IParser"/>,
/// which must subclass one of the existing numeric
/// parsers from <see cref="IFieldCache"/>. Sort type is inferred
/// by testing which numeric parser the parser subclasses. </param>
/// <exception cref="ArgumentException"> if the parser fails to
/// subclass an existing numeric parser, or field is <c>null</c> </exception>
public SortField(string field, FieldCache.IParser parser)
: this(field, parser, false)
{
}
/// <summary>
/// Creates a sort, possibly in reverse, by terms in the given field, parsed
/// to numeric values using a custom <see cref="FieldCache.IParser"/>. </summary>
/// <param name="field"> Name of field to sort by. Must not be <c>null</c>. </param>
/// <param name="parser"> Instance of a <see cref="FieldCache.IParser"/>,
/// which must subclass one of the existing numeric
/// parsers from <see cref="IFieldCache"/>. Sort type is inferred
/// by testing which numeric parser the parser subclasses. </param>
/// <param name="reverse"> <c>True</c> if natural order should be reversed. </param>
/// <exception cref="ArgumentException"> if the parser fails to
/// subclass an existing numeric parser, or field is <c>null</c> </exception>
public SortField(string field, FieldCache.IParser parser, bool reverse)
{
if (parser is FieldCache.IInt32Parser)
{
InitFieldType(field, SortFieldType.INT32);
}
else if (parser is FieldCache.ISingleParser)
{
InitFieldType(field, SortFieldType.SINGLE);
}
#pragma warning disable 612, 618
else if (parser is FieldCache.IInt16Parser)
{
InitFieldType(field, SortFieldType.INT16);
}
else if (parser is FieldCache.IByteParser)
{
InitFieldType(field, SortFieldType.BYTE);
#pragma warning restore 612, 618
}
else if (parser is FieldCache.IInt64Parser)
{
InitFieldType(field, SortFieldType.INT64);
}
else if (parser is FieldCache.IDoubleParser)
{
InitFieldType(field, SortFieldType.DOUBLE);
}
else
{
throw new ArgumentException("Parser instance does not subclass existing numeric parser from FieldCache (got " + parser + ")");
}
this.reverse = reverse;
this.parser = parser;
}
/// <summary>
/// Pass this to <see cref="MissingValue"/> to have missing
/// string values sort first.
/// </summary>
public static readonly object STRING_FIRST = new ObjectAnonymousInnerClassHelper();
private class ObjectAnonymousInnerClassHelper : object
{
public ObjectAnonymousInnerClassHelper()
{
}
public override string ToString()
{
return "SortField.STRING_FIRST";
}
}
/// <summary>
/// Pass this to <see cref="MissingValue"/> to have missing
/// string values sort last.
/// </summary>
public static readonly object STRING_LAST = new ObjectAnonymousInnerClassHelper2();
private class ObjectAnonymousInnerClassHelper2 : object
{
public ObjectAnonymousInnerClassHelper2()
{
}
public override string ToString()
{
return "SortField.STRING_LAST";
}
}
// LUCENENET NOTE: Made this into a property setter above
//public virtual void SetMissingValue(object value)
//{
// if (type == SortFieldType.STRING)
// {
// if (value != STRING_FIRST && value != STRING_LAST)
// {
// throw new ArgumentException("For STRING type, missing value must be either STRING_FIRST or STRING_LAST");
// }
// }
// else if (type != SortFieldType.BYTE && type != SortFieldType.SHORT && type != SortFieldType.INT && type != SortFieldType.FLOAT && type != SortFieldType.LONG && type != SortFieldType.DOUBLE)
// {
// throw new ArgumentException("Missing value only works for numeric or STRING types");
// }
// this.missingValue = value;
//}
/// <summary>
/// Creates a sort with a custom comparison function. </summary>
/// <param name="field"> Name of field to sort by; cannot be <c>null</c>. </param>
/// <param name="comparer"> Returns a comparer for sorting hits. </param>
public SortField(string field, FieldComparerSource comparer)
{
InitFieldType(field, SortFieldType.CUSTOM);
this.comparerSource = comparer;
}
/// <summary>
/// Creates a sort, possibly in reverse, with a custom comparison function. </summary>
/// <param name="field"> Name of field to sort by; cannot be <c>null</c>. </param>
/// <param name="comparer"> Returns a comparer for sorting hits. </param>
/// <param name="reverse"> <c>True</c> if natural order should be reversed. </param>
public SortField(string field, FieldComparerSource comparer, bool reverse)
{
InitFieldType(field, SortFieldType.CUSTOM);
this.reverse = reverse;
this.comparerSource = comparer;
}
// Sets field & type, and ensures field is not NULL unless
// type is SCORE or DOC
private void InitFieldType(string field, SortFieldType type)
{
this.type = type;
if (field == null)
{
if (type != SortFieldType.SCORE && type != SortFieldType.DOC)
{
throw new ArgumentException("field can only be null when type is SCORE or DOC");
}
}
else
{
this.field = field;
}
}
/// <summary>
/// Returns the name of the field. Could return <c>null</c>
/// if the sort is by <see cref="SortFieldType.SCORE"/> or <see cref="SortFieldType.DOC"/>. </summary>
/// <returns> Name of field, possibly <c>null</c>. </returns>
public virtual string Field => field;
/// <summary>
/// Returns the type of contents in the field. </summary>
/// <returns> One of <see cref="SortFieldType.SCORE"/>, <see cref="SortFieldType.DOC"/>,
/// <see cref="SortFieldType.STRING"/>, <see cref="SortFieldType.INT32"/> or <see cref="SortFieldType.SINGLE"/>. </returns>
public virtual SortFieldType Type => type;
/// <summary>
/// Returns the instance of a <see cref="IFieldCache"/> parser that fits to the given sort type.
/// May return <c>null</c> if no parser was specified. Sorting is using the default parser then. </summary>
/// <returns> An instance of a <see cref="IFieldCache"/> parser, or <c>null</c>. </returns>
public virtual FieldCache.IParser Parser => parser;
/// <summary>
/// Returns whether the sort should be reversed. </summary>
/// <returns> <c>True</c> if natural order should be reversed. </returns>
public virtual bool IsReverse => reverse;
/// <summary>
/// Returns the <see cref="FieldComparerSource"/> used for
/// custom sorting.
/// </summary>
public virtual FieldComparerSource ComparerSource => comparerSource;
public override string ToString()
{
StringBuilder buffer = new StringBuilder();
switch (type)
{
case SortFieldType.SCORE:
buffer.Append("<score>");
break;
case SortFieldType.DOC:
buffer.Append("<doc>");
break;
case SortFieldType.STRING:
buffer.Append("<string" + ": \"").Append(field).Append("\">");
break;
case SortFieldType.STRING_VAL:
buffer.Append("<string_val" + ": \"").Append(field).Append("\">");
break;
#pragma warning disable 612, 618
case SortFieldType.BYTE:
buffer.Append("<byte: \"").Append(field).Append("\">");
break;
case SortFieldType.INT16:
#pragma warning restore 612, 618
buffer.Append("<short: \"").Append(field).Append("\">");
break;
case SortFieldType.INT32:
buffer.Append("<int" + ": \"").Append(field).Append("\">");
break;
case SortFieldType.INT64:
buffer.Append("<long: \"").Append(field).Append("\">");
break;
case SortFieldType.SINGLE:
buffer.Append("<float" + ": \"").Append(field).Append("\">");
break;
case SortFieldType.DOUBLE:
buffer.Append("<double" + ": \"").Append(field).Append("\">");
break;
case SortFieldType.CUSTOM:
buffer.Append("<custom:\"").Append(field).Append("\": ").Append(comparerSource).Append('>');
break;
case SortFieldType.REWRITEABLE:
buffer.Append("<rewriteable: \"").Append(field).Append("\">");
break;
default:
buffer.Append("<???: \"").Append(field).Append("\">");
break;
}
if (reverse)
{
buffer.Append('!');
}
if (m_missingValue != null)
{
buffer.Append(" missingValue=");
buffer.Append(m_missingValue);
}
return buffer.ToString();
}
/// <summary>
/// Returns <c>true</c> if <paramref name="o"/> is equal to this. If a
/// <see cref="FieldComparerSource"/> or
/// <see cref="FieldCache.IParser"/> was provided, it must properly
/// implement equals (unless a singleton is always used).
/// </summary>
public override bool Equals(object o)
{
if (this == o)
{
return true;
}
if (!(o is SortField))
{
return false;
}
SortField other = (SortField)o;
return (StringHelper.Equals(other.field, this.field)
&& other.type == this.type
&& other.reverse == this.reverse
&& (other.comparerSource == null ? this.comparerSource == null : other.comparerSource.Equals(this.comparerSource)));
}
/// <summary>
/// Returns a hash code value for this object. If a
/// <see cref="FieldComparerSource"/> or
/// <see cref="FieldCache.IParser"/> was provided, it must properly
/// implement GetHashCode() (unless a singleton is always
/// used).
/// </summary>
public override int GetHashCode()
{
int hash = (int)(type.GetHashCode() ^ 0x346565dd + reverse.GetHashCode() ^ 0xaf5998bb);
if (field != null)
{
hash += (int)(field.GetHashCode() ^ 0xff5685dd);
}
if (comparerSource != null)
{
hash += comparerSource.GetHashCode();
}
return hash;
}
private IComparer<BytesRef> bytesComparer = BytesRef.UTF8SortedAsUnicodeComparer;
public virtual IComparer<BytesRef> BytesComparer
{
get => bytesComparer;
set => bytesComparer = value;
}
/// <summary>
/// Returns the <see cref="FieldComparer"/> to use for
/// sorting.
/// <para/>
/// @lucene.experimental
/// </summary>
/// <param name="numHits"> Number of top hits the queue will store </param>
/// <param name="sortPos"> Position of this <see cref="SortField"/> within
/// <see cref="Sort"/>. The comparer is primary if sortPos==0,
/// secondary if sortPos==1, etc. Some comparers can
/// optimize themselves when they are the primary sort. </param>
/// <returns> <see cref="FieldComparer"/> to use when sorting </returns>
public virtual FieldComparer GetComparer(int numHits, int sortPos)
{
switch (type)
{
case SortFieldType.SCORE:
return new FieldComparer.RelevanceComparer(numHits);
case SortFieldType.DOC:
return new FieldComparer.DocComparer(numHits);
case SortFieldType.INT32:
return new FieldComparer.Int32Comparer(numHits, field, parser, (int?)m_missingValue);
case SortFieldType.SINGLE:
return new FieldComparer.SingleComparer(numHits, field, parser, (float?)m_missingValue);
case SortFieldType.INT64:
return new FieldComparer.Int64Comparer(numHits, field, parser, (long?)m_missingValue);
case SortFieldType.DOUBLE:
return new FieldComparer.DoubleComparer(numHits, field, parser, (double?)m_missingValue);
#pragma warning disable 612, 618
case SortFieldType.BYTE:
return new FieldComparer.ByteComparer(numHits, field, parser, (sbyte?)m_missingValue);
case SortFieldType.INT16:
return new FieldComparer.Int16Comparer(numHits, field, parser, (short?)m_missingValue);
#pragma warning restore 612, 618
case SortFieldType.CUSTOM:
if (Debugging.AssertsEnabled) Debugging.Assert(comparerSource != null);
return comparerSource.NewComparer(field, numHits, sortPos, reverse);
case SortFieldType.STRING:
return new FieldComparer.TermOrdValComparer(numHits, field, m_missingValue == STRING_LAST);
case SortFieldType.STRING_VAL:
// TODO: should we remove this? who really uses it?
return new FieldComparer.TermValComparer(numHits, field);
case SortFieldType.REWRITEABLE:
throw new InvalidOperationException("SortField needs to be rewritten through Sort.rewrite(..) and SortField.rewrite(..)");
default:
throw new InvalidOperationException("Illegal sort type: " + type);
}
}
/// <summary>
/// Rewrites this <see cref="SortField"/>, returning a new <see cref="SortField"/> if a change is made.
/// Subclasses should override this define their rewriting behavior when this
/// SortField is of type <see cref="SortFieldType.REWRITEABLE"/>.
/// <para/>
/// @lucene.experimental
/// </summary>
/// <param name="searcher"> <see cref="IndexSearcher"/> to use during rewriting </param>
/// <returns> New rewritten <see cref="SortField"/>, or <c>this</c> if nothing has changed. </returns>
/// <exception cref="IOException"> Can be thrown by the rewriting </exception>
public virtual SortField Rewrite(IndexSearcher searcher)
{
return this;
}
/// <summary>
/// Whether the relevance score is needed to sort documents. </summary>
public virtual bool NeedsScores => type == SortFieldType.SCORE;
}
/// <summary>
/// Specifies the type of the terms to be sorted, or special types such as CUSTOM
/// </summary>
public enum SortFieldType // LUCENENET NOTE: de-nested and renamed from Type to avoid naming collision with Type property and with System.Type
{
/// <summary>
/// Sort by document score (relevance). Sort values are <see cref="float"/> and higher
/// values are at the front.
/// </summary>
SCORE,
/// <summary>
/// Sort by document number (index order). Sort values are <see cref="int"/> and lower
/// values are at the front.
/// </summary>
DOC,
/// <summary>
/// Sort using term values as <see cref="string"/>s. Sort values are <see cref="string"/>s and lower
/// values are at the front.
/// </summary>
STRING,
/// <summary>
/// Sort using term values as encoded <see cref="int"/>s. Sort values are <see cref="int"/> and
/// lower values are at the front.
/// <para/>
/// NOTE: This was INT in Lucene
/// </summary>
INT32,
/// <summary>
/// Sort using term values as encoded <see cref="float"/>s. Sort values are <see cref="float"/> and
/// lower values are at the front.
/// <para/>
/// NOTE: This was FLOAT in Lucene
/// </summary>
SINGLE,
/// <summary>
/// Sort using term values as encoded <see cref="long"/>s. Sort values are <see cref="long"/> and
/// lower values are at the front.
/// <para/>
/// NOTE: This was LONG in Lucene
/// </summary>
INT64,
/// <summary>
/// Sort using term values as encoded <see cref="double"/>s. Sort values are <see cref="double"/> and
/// lower values are at the front.
/// </summary>
DOUBLE,
/// <summary>
/// Sort using term values as encoded <see cref="short"/>s. Sort values are <see cref="short"/> and
/// lower values are at the front.
/// <para/>
/// NOTE: This was SHORT in Lucene
/// </summary>
[System.Obsolete]
INT16,
/// <summary>
/// Sort using a custom <see cref="IComparer{T}"/>. Sort values are any <see cref="IComparable{T}"/> and
/// sorting is done according to natural order.
/// </summary>
CUSTOM,
/// <summary>
/// Sort using term values as encoded <see cref="byte"/>s. Sort values are <see cref="byte"/> and
/// lower values are at the front.
/// </summary>
[System.Obsolete]
BYTE,
/// <summary>
/// Sort using term values as <see cref="string"/>s, but comparing by
/// value (using <see cref="BytesRef.CompareTo(BytesRef)"/>) for all comparisons.
/// this is typically slower than <see cref="STRING"/>, which
/// uses ordinals to do the sorting.
/// </summary>
STRING_VAL,
/// <summary>
/// Sort use <see cref="T:byte[]"/> index values. </summary>
BYTES,
/// <summary>
/// Force rewriting of <see cref="SortField"/> using <see cref="SortField.Rewrite(IndexSearcher)"/>
/// before it can be used for sorting
/// </summary>
REWRITEABLE
}
}