1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 using System;
19 using Lucene.Net.Util;
20 
21 namespace Lucene.Net.Search
22 {
23 
24 	/// <summary> Expert: A hit queue for sorting by hits by terms in more than one field.
25 	/// Uses <c>FieldCache.DEFAULT</c> for maintaining
26 	/// internal term lookup tables.
27 	///
28 	/// <b>NOTE:</b> This API is experimental and might change in
29 	/// incompatible ways in the next release.
30 	///
31 	/// </summary>
32 	/// <seealso cref="Searcher.Search(Query,Filter,int,Sort)"></seealso>
33 	/// <seealso cref="FieldCache"></seealso>
34 	public abstract class FieldValueHitQueue : PriorityQueue<FieldValueHitQueue.Entry>
35 	{
36         // had to change from internal to public, due to public accessability of FieldValueHitQueue
37 		public /*internal*/ sealed class Entry : ScoreDoc
38 		{
39 			internal int slot;
40 
Entry(int slot, int doc, float score)41             internal Entry(int slot, int doc, float score)
42                 : base(doc, score)
43 			{
44 
45 				this.slot = slot;
46 			}
47 
ToString()48 			public override System.String ToString()
49 			{
50 				return "slot:" + slot + " " + base.ToString();
51 			}
52 		}
53 
54 		/// <summary> An implementation of <see cref="FieldValueHitQueue" /> which is optimized in case
55 		/// there is just one comparator.
56 		/// </summary>
57 		private sealed class OneComparatorFieldValueHitQueue : FieldValueHitQueue
58 		{
59 
60 			private FieldComparator comparator;
61 			private int oneReverseMul;
62 
OneComparatorFieldValueHitQueue(SortField[] fields, int size)63 			public OneComparatorFieldValueHitQueue(SortField[] fields, int size):base(fields)
64 			{
65 				if (fields.Length == 0)
66 				{
67 					throw new System.ArgumentException("Sort must contain at least one field");
68 				}
69 
70 				SortField field = fields[0];
71 				comparator = field.GetComparator(size, 0);
72 				oneReverseMul = field.reverse?- 1:1;
73 
74 				comparators[0] = comparator;
75 				reverseMul[0] = oneReverseMul;
76 
77 				Initialize(size);
78 			}
79 
80 			/// <summary> Returns whether <c>a</c> is less relevant than <c>b</c>.</summary>
81 			/// <param name="hitA">ScoreDoc</param>
82 			/// <param name="hitB">ScoreDoc</param>
83 			/// <returns><c>true</c> if document <c>a</c> should be sorted after document <c>b</c>.</returns>
LessThan(Entry hitA, Entry hitB)84             public override bool LessThan(Entry hitA, Entry hitB)
85 			{
86 				System.Diagnostics.Debug.Assert(hitA != hitB);
87 				System.Diagnostics.Debug.Assert(hitA.slot != hitB.slot);
88 
89 				int c = oneReverseMul * comparator.Compare(hitA.slot, hitB.slot);
90 				if (c != 0)
91 				{
92 					return c > 0;
93 				}
94 
95 				// avoid random sort order that could lead to duplicates (bug #31241):
96                 return hitA.Doc > hitB.Doc;
97 			}
98 		}
99 
100 		/// <summary> An implementation of <see cref="FieldValueHitQueue" /> which is optimized in case
101 		/// there is more than one comparator.
102 		/// </summary>
103 		private sealed class MultiComparatorsFieldValueHitQueue : FieldValueHitQueue
104 		{
105 
MultiComparatorsFieldValueHitQueue(SortField[] fields, int size)106 			public MultiComparatorsFieldValueHitQueue(SortField[] fields, int size):base(fields)
107 			{
108 
109 				int numComparators = comparators.Length;
110 				for (int i = 0; i < numComparators; ++i)
111 				{
112 					SortField field = fields[i];
113 
114 					reverseMul[i] = field.reverse?- 1:1;
115 					comparators[i] = field.GetComparator(size, i);
116 				}
117 
118 				Initialize(size);
119 			}
120 
LessThan(Entry hitA, Entry hitB)121             public override bool LessThan(Entry hitA, Entry hitB)
122 			{
123 				System.Diagnostics.Debug.Assert(hitA != hitB);
124 				System.Diagnostics.Debug.Assert(hitA.slot != hitB.slot);
125 
126 				int numComparators = comparators.Length;
127 				for (int i = 0; i < numComparators; ++i)
128 				{
129 					int c = reverseMul[i] * comparators[i].Compare(hitA.slot, hitB.slot);
130 					if (c != 0)
131 					{
132 						// Short circuit
133 						return c > 0;
134 					}
135 				}
136 
137 				// avoid random sort order that could lead to duplicates (bug #31241):
138                 return hitA.Doc > hitB.Doc;
139 			}
140 		}
141 
142 		// prevent instantiation and extension.
FieldValueHitQueue(SortField[] fields)143 		private FieldValueHitQueue(SortField[] fields)
144 		{
145 			// When we get here, fields.length is guaranteed to be > 0, therefore no
146 			// need to check it again.
147 
148 			// All these are required by this class's API - need to return arrays.
149 			// Therefore even in the case of a single comparator, create an array
150 			// anyway.
151 			this.fields = fields;
152 			int numComparators = fields.Length;
153 			comparators = new FieldComparator[numComparators];
154 			reverseMul = new int[numComparators];
155 		}
156 
157 		/// <summary> Creates a hit queue sorted by the given list of fields.
158 		///
159 		/// <p/><b>NOTE</b>: The instances returned by this method
160 		/// pre-allocate a full array of length <c>numHits</c>.
161 		///
162 		/// </summary>
163 		/// <param name="fields">SortField array we are sorting by in priority order (highest
164 		/// priority first); cannot be <c>null</c> or empty
165 		/// </param>
166 		/// <param name="size">The number of hits to retain. Must be greater than zero.
167 		/// </param>
168 		/// <throws>  IOException </throws>
Create(SortField[] fields, int size)169 		public static FieldValueHitQueue Create(SortField[] fields, int size)
170 		{
171 
172 			if (fields.Length == 0)
173 			{
174 				throw new System.ArgumentException("Sort must contain at least one field");
175 			}
176 
177 			if (fields.Length == 1)
178 			{
179 				return new OneComparatorFieldValueHitQueue(fields, size);
180 			}
181 			else
182 			{
183 				return new MultiComparatorsFieldValueHitQueue(fields, size);
184 			}
185 		}
186 
GetComparators()187 		internal virtual FieldComparator[] GetComparators()
188 		{
189 			return comparators;
190 		}
191 
GetReverseMul()192 		internal virtual int[] GetReverseMul()
193 		{
194 			return reverseMul;
195 		}
196 
197 		/// <summary>Stores the sort criteria being used. </summary>
198 		protected internal SortField[] fields;
199 		protected internal FieldComparator[] comparators;
200 		protected internal int[] reverseMul;
201 
LessThan(Entry a, Entry b)202         public abstract override bool LessThan(Entry a, Entry b);
203 
204 		/// <summary> Given a queue Entry, creates a corresponding FieldDoc
205 		/// that contains the values used to sort the given document.
206 		/// These values are not the raw values out of the index, but the internal
207 		/// representation of them. This is so the given search hit can be collated by
208 		/// a MultiSearcher with other search hits.
209 		///
210 		/// </summary>
211 		/// <param name="entry">The Entry used to create a FieldDoc
212 		/// </param>
213 		/// <returns> The newly created FieldDoc
214 		/// </returns>
215 		/// <seealso cref="Searchable.Search(Weight,Filter,int,Sort)">
216 		/// </seealso>
FillFields(Entry entry)217 		internal virtual FieldDoc FillFields(Entry entry)
218 		{
219 			int n = comparators.Length;
220 			System.IComparable[] fields = new System.IComparable[n];
221 			for (int i = 0; i < n; ++i)
222 			{
223 				fields[i] = comparators[i][entry.slot];
224 			}
225 			//if (maxscore > 1.0f) doc.score /= maxscore;   // normalize scores
226 			return new FieldDoc(entry.Doc, entry.Score, fields);
227 		}
228 
229 		/// <summary>Returns the SortFields being used by this hit queue. </summary>
GetFields()230 		internal virtual SortField[] GetFields()
231 		{
232 			return fields;
233 		}
234 	}
235 }