Skip to content

Commit 6ff8800

Browse files
authored
BREAKING: Remove covariance and LINQ use from Grouping, apache#1059 (apache#1066)
* Break GroupingSearch into subclasses, apache#1059 This still needs XML doc comments, but this breaks the GroupingSearch "god class" into three subclasses that implement a common abstract class. This should allow us to not need covariant interfaces for the return types. In order to randomly switch between these classes with incompatible generic type arguments, the test shows how you can use a delegate to work around this limitation. * Fix failing tests * Fix more failing unit tests; refactor out common AbstractFieldOrFunctionGroupingSearch<T> class * Remove IAbstractAllGroupsCollector<T> interface * Make AbstractAllGroupsCollector.Groups be ICollection, remove some LINQ use * Remove ITopGroups and IAbstractSecondPassGroupingCollector interfaces * Remove IGroupDocs interface * Remove IAbstractFirstPassGroupingCollector and ISearchGroup interfaces, one failing test * Remove IAbstractDistinctValuesCollector interface * Remove IGroupCount interface * Remove IGroupCountCollector interface * Remove IEnumerable/LINQ use in Grouping * Remove some O(n) extra list creation * Remove some more IEnumerable spots * Refactor to remove wrapping types and add back non-generic interfaces for collectors * Return null if TopGroups returns null * Add some XML doc comments * Add back removed XML comments during refactoring * Add XML doc comments to GroupingSearch * Add IAbstractGroupingSearch interface for symmetry with the other types * Add .idea/ folder to .gitignore * Remove unnecessary cast * Remove unnecessary non-generic overload of GetTopGroups * Use CastingEnumeratorAdapter in CastingListAdapter; optimize as readonly struct to reduce allocations * Remove UTF-8 BOMs * Fix fluent API break due to base class; update docs and add tests * Make ByDocBlock/DocBlockGroupingSearch non-generic to match Java's wildcard generic erasure for null * Exclude Lucene3x codec from random grouping tests * Remove setter for ISearchGroup.GroupValue * Cast each element of CastingSetAdapter instead of casting the whole enumerable * Remove BOM * Rename abstract classes and refactor into a template method pattern
1 parent 22247b0 commit 6ff8800

34 files changed

Lines changed: 1861 additions & 866 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ packages/
3636
.vs/
3737
*.lock.json
3838
TestResults/
39+
.idea/
3940
test-files/analysis/data/
4041
[Nn]u[Gg]et[Pp]ackages/
4142
out.dot

src/Lucene.Net.Grouping/AbstractAllGroupsCollector.cs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using Lucene.Net.Index;
2+
using Lucene.Net.Support;
23
using System.Collections.Generic;
3-
using System.Linq;
44

55
namespace Lucene.Net.Search.Grouping
66
{
@@ -34,14 +34,14 @@ namespace Lucene.Net.Search.Grouping
3434
/// @lucene.experimental
3535
/// </summary>
3636
/// <typeparam name="TGroupValue"></typeparam>
37-
public abstract class AbstractAllGroupsCollector<TGroupValue> : IAbstractAllGroupsCollector<TGroupValue>
37+
public abstract class AbstractAllGroupsCollector<TGroupValue> : IAbstractAllGroupsCollector
3838
{
3939
/// <summary>
4040
/// Returns the total number of groups for the executed search.
4141
/// This is a convenience method. The following code snippet has the same effect: <code>GetGroups().Count</code>
4242
/// </summary>
4343
/// <returns>The total number of groups for the executed search</returns>
44-
public virtual int GroupCount => Groups.Count();
44+
public virtual int GroupCount => Groups.Count;
4545

4646
/// <summary>
4747
/// Returns the group values
@@ -51,7 +51,7 @@ public abstract class AbstractAllGroupsCollector<TGroupValue> : IAbstractAllGrou
5151
/// </para>
5252
/// </summary>
5353
/// <returns>the group values</returns>
54-
public abstract IEnumerable<TGroupValue> Groups { get; }
54+
public abstract ICollection<TGroupValue> Groups { get; }
5555

5656

5757
// Empty not necessary
@@ -88,29 +88,34 @@ public virtual void SetScorer(Scorer scorer)
8888
public abstract void SetNextReader(AtomicReaderContext context);
8989

9090
public virtual bool AcceptsDocsOutOfOrder => true;
91+
92+
#region Explicit interface implementations
93+
94+
/// <summary>
95+
/// LUCENENET specific method to provide an object-based implementation of <see cref="Groups"/>.
96+
/// </summary>
97+
ICollection<object> IAbstractAllGroupsCollector.Groups => new CastingCollectionAdapter<TGroupValue, object>(Groups);
98+
99+
#endregion
91100
}
92101

93102
/// <summary>
94-
/// LUCENENET specific interface used to apply covariance to TGroupValue
103+
/// LUCENENET specific interface to provide a non-generic abstraction
104+
/// for <see cref="AbstractAllGroupsCollector{TGroupValue}"/>.
95105
/// </summary>
96-
/// <typeparam name="TGroupValue"></typeparam>
97-
public interface IAbstractAllGroupsCollector<out TGroupValue> : ICollector
106+
public interface IAbstractAllGroupsCollector : ICollector
98107
{
99108
/// <summary>
100109
/// Returns the total number of groups for the executed search.
101-
/// This is a convenience method. The following code snippet has the same effect: <code>GetGroups().Count</code>
102110
/// </summary>
103-
/// <returns>The total number of groups for the executed search</returns>
104111
int GroupCount { get; }
105112

106113
/// <summary>
107114
/// Returns the group values
108-
/// <para>
115+
/// <para />
109116
/// This is an unordered collections of group values. For each group that matched the query there is a <see cref="Util.BytesRef"/>
110117
/// representing a group value.
111-
/// </para>
112118
/// </summary>
113-
/// <returns>the group values</returns>
114-
IEnumerable<TGroupValue> Groups { get; }
119+
ICollection<object> Groups { get; }
115120
}
116121
}

src/Lucene.Net.Grouping/AbstractDistinctValuesCollector.cs

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Lucene.Net.Index;
2+
using Lucene.Net.Support;
23
using System.Collections.Generic;
34
using JCG = J2N.Collections.Generic;
45

@@ -26,15 +27,37 @@ namespace Lucene.Net.Search.Grouping
2627
///
2728
/// @lucene.experimental
2829
/// </summary>
29-
/// <typeparam name="GC"></typeparam>
30-
public abstract class AbstractDistinctValuesCollector<GC> : IAbstractDistinctValuesCollector<GC>
31-
where GC : AbstractDistinctValuesCollector.IGroupCount<object>
30+
/// <typeparam name="GC">The type of group counts</typeparam>
31+
/// <typeparam name="TGroupValue">The type of the group values</typeparam>
32+
/// <remarks>
33+
/// The <typeparamref name="TGroupValue"/> type parameter is LUCENENET specific to allow for
34+
/// strongly-typed group values.
35+
/// <para/>
36+
/// <b>Migration from 4.8.0-beta00017 and earlier:</b> This class previously had a single type
37+
/// parameter <c>GC</c> with a constraint of
38+
/// <c>AbstractDistinctValuesCollector.IGroupCount&lt;object&gt;</c>. It now has two type
39+
/// parameters: <typeparamref name="GC"/> and <typeparamref name="TGroupValue"/>. When migrating,
40+
/// add the group value type (typically <c>BytesRef</c> or <c>MutableValue</c>) as the second
41+
/// type argument. For example:
42+
/// <code>
43+
/// // Before:
44+
/// AbstractDistinctValuesCollector&lt;MyGroupCount&gt;
45+
///
46+
/// // After:
47+
/// AbstractDistinctValuesCollector&lt;MyGroupCount, BytesRef&gt;
48+
/// </code>
49+
/// Additionally, the <c>GC</c> constraint changed from
50+
/// <c>AbstractDistinctValuesCollector.IGroupCount&lt;object&gt;</c> to
51+
/// <c>AbstractDistinctValuesCollector.GroupCount&lt;TGroupValue&gt;</c>.
52+
/// </remarks>
53+
public abstract class AbstractDistinctValuesCollector<GC, TGroupValue> : IAbstractDistinctValuesCollector
54+
where GC : AbstractDistinctValuesCollector.GroupCount<TGroupValue>
3255
{
3356
/// <summary>
3457
/// Returns all unique values for each top N group.
3558
/// </summary>
3659
/// <returns>all unique values for each top N group</returns>
37-
public abstract IEnumerable<GC> Groups { get; }
60+
public abstract IList<GC> Groups { get; }
3861

3962
public virtual bool AcceptsDocsOutOfOrder => true;
4063

@@ -69,6 +92,16 @@ public virtual void SetScorer(Scorer scorer)
6992
/// </summary>
7093
/// <param name="context">next atomic reader context </param>
7194
public abstract void SetNextReader(AtomicReaderContext context);
95+
96+
#region Explicit interface implementations
97+
98+
/// <summary>
99+
/// LUCENENET specific implementation to provide a non-generic abstraction
100+
/// </summary>
101+
IList<AbstractDistinctValuesCollector.IGroupCount> IAbstractDistinctValuesCollector.Groups
102+
=> new CastingListAdapter<GC, AbstractDistinctValuesCollector.IGroupCount>(Groups);
103+
104+
#endregion
72105
}
73106

74107
/// <summary>
@@ -80,48 +113,61 @@ public virtual void SetScorer(Scorer scorer)
80113
public static class AbstractDistinctValuesCollector // LUCENENET specific: CA1052 Static holder types should be Static or NotInheritable
81114
{
82115
/// <summary>
83-
/// Returned by <see cref="AbstractDistinctValuesCollector{GC}.Groups"/>,
116+
/// Returned by <see cref="AbstractDistinctValuesCollector{GC, TGroupValue}.Groups"/>,
84117
/// representing the value and set of distinct values for the group.
85118
/// </summary>
86119
/// <typeparam name="TGroupValue"></typeparam>
87120
/// <remarks>
88121
/// LUCENENET - removed this class from being a nested class of
89-
/// <see cref="AbstractDistinctValuesCollector{GC}"/> and renamed
90-
/// from GroupCount to AbstractGroupCount
122+
/// <see cref="AbstractDistinctValuesCollector{GC, TGroupValue}"/>
91123
/// </remarks>
92-
public abstract class GroupCount<TGroupValue> : IGroupCount<TGroupValue>
124+
public abstract class GroupCount<TGroupValue> : IGroupCount
93125
{
94126
public TGroupValue GroupValue { get; protected set; }
95-
public IEnumerable<TGroupValue> UniqueValues { get; protected set; }
127+
public ISet<TGroupValue> UniqueValues { get; protected set; }
96128

97129
protected GroupCount(TGroupValue groupValue) // LUCENENET: CA1012: Abstract types should not have constructors (marked protected)
98130
{
99131
this.GroupValue = groupValue;
100132
this.UniqueValues = new JCG.HashSet<TGroupValue>();
101133
}
134+
135+
#region Explicit interface implementations
136+
137+
/// <summary>
138+
/// LUCENENET specific method to provide an object-based implementation of <see cref="GroupValue"/>.
139+
/// </summary>
140+
object IGroupCount.GroupValue => GroupValue;
141+
142+
/// <summary>
143+
/// LUCENENET specific method to provide an object-based implementation of <see cref="UniqueValues"/>.
144+
/// </summary>
145+
ISet<object> IGroupCount.UniqueValues => new CastingSetAdapter<TGroupValue, object>(UniqueValues);
146+
147+
#endregion
102148
}
103149

104150
/// <summary>
105-
/// LUCENENET specific interface used to apply covariance to TGroupValue
151+
/// LUCENENET specific interface to provide a non-generic abstraction
152+
/// for <see cref="GroupCount{TGroupValue}"/>.
106153
/// </summary>
107-
/// <typeparam name="TGroupValue"></typeparam>
108-
public interface IGroupCount<out TGroupValue>
154+
public interface IGroupCount
109155
{
110-
TGroupValue GroupValue { get; }
111-
IEnumerable<TGroupValue> UniqueValues { get; }
156+
object GroupValue { get; }
157+
158+
ISet<object> UniqueValues { get; }
112159
}
113160
}
114161

115162
/// <summary>
116-
/// LUCENENET specific interface used to apply covariance to GC
163+
/// LUCENENET specific interface to provide a non-generic abstraction
164+
/// for <see cref="AbstractDistinctValuesCollector{GC, TGroupValue}"/>.
117165
/// </summary>
118-
/// <typeparam name="GC"></typeparam>
119-
public interface IAbstractDistinctValuesCollector<out GC> : ICollector
166+
public interface IAbstractDistinctValuesCollector : ICollector
120167
{
121168
/// <summary>
122169
/// Returns all unique values for each top N group.
123170
/// </summary>
124-
/// <returns>all unique values for each top N group</returns>
125-
IEnumerable<GC> Groups { get; }
171+
IList<AbstractDistinctValuesCollector.IGroupCount> Groups { get; }
126172
}
127173
}

src/Lucene.Net.Grouping/AbstractFirstPassGroupingCollector.cs

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
using Lucene.Net.Diagnostics;
22
using Lucene.Net.Index;
3+
using Lucene.Net.Support;
34
using Lucene.Net.Support.Threading;
45
using System;
56
using System.Collections.Generic;
6-
using System.Diagnostics;
77
using System.IO;
88
using System.Linq;
99
using JCG = J2N.Collections.Generic;
@@ -40,7 +40,7 @@ namespace Lucene.Net.Search.Grouping
4040
/// @lucene.experimental
4141
/// </summary>
4242
/// <typeparam name="TGroupValue"></typeparam>
43-
public abstract class AbstractFirstPassGroupingCollector<TGroupValue> : IAbstractFirstPassGroupingCollector<TGroupValue>
43+
public abstract class AbstractFirstPassGroupingCollector<TGroupValue> : IAbstractFirstPassGroupingCollector
4444
{
4545
private readonly Sort groupSort;
4646
private readonly FieldComparer[] comparers;
@@ -106,7 +106,7 @@ protected AbstractFirstPassGroupingCollector(Sort groupSort, int topNGroups) //
106106
/// <param name="groupOffset">The offset in the collected groups</param>
107107
/// <param name="fillFields">Whether to fill to <see cref="SearchGroup{TGroupValue}.SortValues"/></param>
108108
/// <returns>top groups, starting from offset</returns>
109-
public virtual IEnumerable<ISearchGroup<TGroupValue>> GetTopGroups(int groupOffset, bool fillFields)
109+
public virtual ICollection<SearchGroup<TGroupValue>> GetTopGroups(int groupOffset, bool fillFields)
110110
{
111111

112112
//System.out.println("FP.getTopGroups groupOffset=" + groupOffset + " fillFields=" + fillFields + " groupMap.size()=" + groupMap.size());
@@ -126,7 +126,7 @@ public virtual IEnumerable<ISearchGroup<TGroupValue>> GetTopGroups(int groupOffs
126126
BuildSortedSet();
127127
}
128128

129-
ICollection<ISearchGroup<TGroupValue>> result = new JCG.List<ISearchGroup<TGroupValue>>();
129+
ICollection<SearchGroup<TGroupValue>> result = new JCG.List<SearchGroup<TGroupValue>>();
130130
int upto = 0;
131131
int sortFieldCount = groupSort.GetSort().Length;
132132
foreach (CollectedSearchGroup<TGroupValue> group in m_orderedGroups)
@@ -421,27 +421,39 @@ public virtual void SetNextReader(AtomicReaderContext context)
421421
/// <returns>a copy of the specified group value</returns>
422422
protected abstract TGroupValue CopyDocGroupValue(TGroupValue groupValue, TGroupValue reuse);
423423

424+
#region Explicit interface implementations
425+
426+
/// <summary>
427+
/// LUCENENET specific method to provide an <see cref="ISearchGroup"/>-based implementation of <see cref="GetTopGroups(int, bool)"/>.
428+
/// </summary>
429+
/// <param name="groupOffset">The offset in the collected groups</param>
430+
/// <param name="fillFields">Whether to fill to <see cref="ISearchGroup.SortValues"/></param>
431+
/// <returns>top groups, starting from offset</returns>
432+
ICollection<ISearchGroup> IAbstractFirstPassGroupingCollector.GetTopGroups(int groupOffset, bool fillFields)
433+
{
434+
var topGroups = GetTopGroups(groupOffset, fillFields);
435+
return topGroups != null
436+
? new CastingCollectionAdapter<SearchGroup<TGroupValue>, ISearchGroup>(topGroups)
437+
: null;
438+
}
439+
440+
#endregion
424441
}
425442

426443
/// <summary>
427-
/// LUCENENET specific interface used to apply covariance to TGroupValue
428-
/// to simulate Java's wildcard generics.
444+
/// LUCENENET specific interface to provide a non-generic abstraction
445+
/// for <see cref="AbstractFirstPassGroupingCollector{TGroupValue}"/>.
429446
/// </summary>
430-
/// <typeparam name="TGroupValue"></typeparam>
431-
public interface IAbstractFirstPassGroupingCollector<out TGroupValue> : ICollector
447+
public interface IAbstractFirstPassGroupingCollector : ICollector
432448
{
433449
/// <summary>
434450
/// Returns top groups, starting from offset. This may
435451
/// return null, if no groups were collected, or if the
436452
/// number of unique groups collected is &lt;= offset.
437453
/// </summary>
438454
/// <param name="groupOffset">The offset in the collected groups</param>
439-
/// <param name="fillFields">Whether to fill to <see cref="SearchGroup{TGroupValue}.SortValues"/></param>
455+
/// <param name="fillFields">Whether to fill to <see cref="ISearchGroup.SortValues"/></param>
440456
/// <returns>top groups, starting from offset</returns>
441-
/// <remarks>
442-
/// LUCENENET NOTE: We must use <see cref="IEnumerable{TGroupValue}"/> rather than
443-
/// <see cref="ICollection{TGroupValue}"/> here because we need this to be covariant
444-
/// </remarks>
445-
IEnumerable<ISearchGroup<TGroupValue>> GetTopGroups(int groupOffset, bool fillFields);
457+
ICollection<ISearchGroup> GetTopGroups(int groupOffset, bool fillFields);
446458
}
447459
}

0 commit comments

Comments
 (0)