lucenenet-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From nightowl...@apache.org
Subject [40/58] [abbrv] lucenenet git commit: Added interfaces to GroupDocs, SearchGroup, and TopGroups to apply covariance so they act more like the wildcard generics that were used in Java.
Date Thu, 10 Nov 2016 11:33:51 GMT
Added interfaces to GroupDocs, SearchGroup, and TopGroups to apply covariance so they act more like the wildcard generics that were used in Java.


Project: http://git-wip-us.apache.org/repos/asf/lucenenet/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucenenet/commit/0a137bea
Tree: http://git-wip-us.apache.org/repos/asf/lucenenet/tree/0a137bea
Diff: http://git-wip-us.apache.org/repos/asf/lucenenet/diff/0a137bea

Branch: refs/heads/grouping
Commit: 0a137bea5285ac0022861b67339665ace2ae9f49
Parents: a8f7f42
Author: Shad Storhaug <shad@shadstorhaug.com>
Authored: Wed Nov 2 20:37:20 2016 +0700
Committer: Shad Storhaug <shad@shadstorhaug.com>
Committed: Tue Nov 8 02:24:55 2016 +0700

----------------------------------------------------------------------
 .../AbstractAllGroupHeadsCollector.cs           |  42 +--
 .../AbstractAllGroupsCollector.cs               |  65 +++--
 .../AbstractFirstPassGroupingCollector.cs       |  41 ++-
 .../AbstractSecondPassGroupingCollector.cs      |  25 +-
 .../Function/FunctionAllGroupsCollector.cs      |   2 +-
 .../Function/FunctionDistinctValuesCollector.cs |   2 +-
 src/Lucene.Net.Grouping/GroupDocs.cs            |  57 +++-
 src/Lucene.Net.Grouping/GroupingSearch.cs       | 286 ++++++++++---------
 src/Lucene.Net.Grouping/SearchGroup.cs          |  47 +--
 .../Term/TermAllGroupsCollector.cs              |   2 +-
 .../Term/TermDistinctValuesCollector.cs         |   2 +-
 src/Lucene.Net.Grouping/TopGroups.cs            |  62 +++-
 12 files changed, 392 insertions(+), 241 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucenenet/blob/0a137bea/src/Lucene.Net.Grouping/AbstractAllGroupHeadsCollector.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Grouping/AbstractAllGroupHeadsCollector.cs b/src/Lucene.Net.Grouping/AbstractAllGroupHeadsCollector.cs
index a3ae1bf..f27d2e4 100644
--- a/src/Lucene.Net.Grouping/AbstractAllGroupHeadsCollector.cs
+++ b/src/Lucene.Net.Grouping/AbstractAllGroupHeadsCollector.cs
@@ -224,22 +224,28 @@ namespace Lucene.Net.Search.Grouping
         protected abstract void RetrieveGroupHeadAndAddIfNotExist(int doc);
     }
 
-    /////// <summary>
-    /////// LUCENENET specific interface used to reference an 
-    /////// <see cref="AbstractAllGroupHeadsCollector{GH}"/> subclass
-    /////// without refering to its generic closing type.
-    /////// </summary>
-    ////public interface IAllGroupHeadsCollector
-    ////{
-    ////    // From AbstractAllGroupHeadsCollector{GH}
-    ////    FixedBitSet RetrieveGroupHeads(int maxDoc);
-    ////    int[] RetrieveGroupHeads();
-    ////    int GroupHeadsSize { get; }
-    ////    void Collect(int doc);
-    ////    bool AcceptsDocsOutOfOrder();
-
-    ////    // From Collector
-    ////    Scorer Scorer { set; }
-    ////    AtomicReaderContext NextReader { set; }
-    ////}
+    ///// <summary>
+    ///// LUCENENET specific interface used to apply covariance to GH
+    ///// </summary>
+    //public interface IAbstractAllGroupHeadsCollector<out GH>
+    //{
+    //    /// <summary>
+    //    /// 
+    //    /// </summary>
+    //    /// <param name="maxDoc">The maxDoc of the top level <see cref="Index.IndexReader"/></param>
+    //    /// <returns>a <see cref="FixedBitSet"/> containing all group heads.</returns>
+    //    FixedBitSet RetrieveGroupHeads(int maxDoc);
+
+    //    /// <summary>
+    //    /// 
+    //    /// </summary>
+    //    /// <returns>an int array containing all group heads. The size of the array is equal to number of collected unique groups.</returns>
+    //    int[] RetrieveGroupHeads();
+
+    //    /// <summary>
+    //    /// 
+    //    /// </summary>
+    //    /// <returns>the number of group heads found for a query.</returns>
+    //    int GroupHeadsSize { get; }
+    //}
 }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/0a137bea/src/Lucene.Net.Grouping/AbstractAllGroupsCollector.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Grouping/AbstractAllGroupsCollector.cs b/src/Lucene.Net.Grouping/AbstractAllGroupsCollector.cs
index 0c8bc14..df240ee 100644
--- a/src/Lucene.Net.Grouping/AbstractAllGroupsCollector.cs
+++ b/src/Lucene.Net.Grouping/AbstractAllGroupsCollector.cs
@@ -1,4 +1,5 @@
 ´╗┐using System.Collections.Generic;
+using System.Linq;
 
 namespace Lucene.Net.Search.Grouping
 {
@@ -15,18 +16,18 @@ namespace Lucene.Net.Search.Grouping
     /// @lucene.experimental
     /// </summary>
     /// <typeparam name="TGroupValue"></typeparam>
-    public abstract class AbstractAllGroupsCollector<TGroupValue> : AbstractAllGroupsCollector
+    public abstract class AbstractAllGroupsCollector<TGroupValue> : Collector, IAbstractAllGroupsCollector<TGroupValue>
     {
         /// <summary>
         /// Returns the total number of groups for the executed search.
         /// This is a convenience method. The following code snippet has the same effect: <code>GetGroups().Count</code>
         /// </summary>
         /// <returns>The total number of groups for the executed search</returns>
-        public override int GroupCount
+        public virtual int GroupCount
         {
             get
             {
-                return Groups.Count;
+                return Groups.Count();
             }
         }
 
@@ -38,7 +39,7 @@ namespace Lucene.Net.Search.Grouping
         /// </para>
         /// </summary>
         /// <returns>the group values</returns>
-        public abstract ICollection<TGroupValue> Groups { get; }
+        public abstract IEnumerable<TGroupValue> Groups { get; }
 
 
         // Empty not necessary
@@ -56,30 +57,54 @@ namespace Lucene.Net.Search.Grouping
     }
 
     /// <summary>
-    /// LUCENENET specific class used to reference <see cref="AbstractAllGroupsCollector{TGroupValue}"/>
-    /// without refering to its generic closing type.
+    /// LUCENENET specific interface used to apply covariance to TGroupValue
     /// </summary>
-    public abstract class AbstractAllGroupsCollector : Collector
+    /// <typeparam name="TGroupValue"></typeparam>
+    public interface IAbstractAllGroupsCollector<out TGroupValue>
     {
         /// <summary>
         /// Returns the total number of groups for the executed search.
         /// This is a convenience method. The following code snippet has the same effect: <code>GetGroups().Count</code>
         /// </summary>
         /// <returns>The total number of groups for the executed search</returns>
-        public abstract int GroupCount { get; }
+        int GroupCount { get; }
 
+        /// <summary>
+        /// Returns the group values
+        /// <para>
+        /// This is an unordered collections of group values. For each group that matched the query there is a <see cref="BytesRef"/>
+        /// representing a group value.
+        /// </para>
+        /// </summary>
+        /// <returns>the group values</returns>
+        IEnumerable<TGroupValue> Groups { get; }
+    }
 
-        // Empty not necessary
-        public override Scorer Scorer
-        {
-            set
-            {
-            }
-        }
+    ///// <summary>
+    ///// LUCENENET specific class used to reference <see cref="AbstractAllGroupsCollector{TGroupValue}"/>
+    ///// without refering to its generic closing type.
+    ///// </summary>
+    //public abstract class AbstractAllGroupsCollector : Collector
+    //{
+    //    /// <summary>
+    //    /// Returns the total number of groups for the executed search.
+    //    /// This is a convenience method. The following code snippet has the same effect: <code>GetGroups().Count</code>
+    //    /// </summary>
+    //    /// <returns>The total number of groups for the executed search</returns>
+    //    public abstract int GroupCount { get; }
 
-        public override bool AcceptsDocsOutOfOrder()
-        {
-            return true;
-        }
-    }
+
+    //    // Empty not necessary
+    //    public override Scorer Scorer
+    //    {
+    //        set
+    //        {
+    //        }
+    //    }
+
+    //    public override bool AcceptsDocsOutOfOrder()
+    //    {
+    //        return true;
+    //    }
+    //}
 }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/0a137bea/src/Lucene.Net.Grouping/AbstractFirstPassGroupingCollector.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Grouping/AbstractFirstPassGroupingCollector.cs b/src/Lucene.Net.Grouping/AbstractFirstPassGroupingCollector.cs
index 8efafd2..0fb682d 100644
--- a/src/Lucene.Net.Grouping/AbstractFirstPassGroupingCollector.cs
+++ b/src/Lucene.Net.Grouping/AbstractFirstPassGroupingCollector.cs
@@ -22,7 +22,7 @@ namespace Lucene.Net.Search.Grouping
     /// @lucene.experimental
     /// </summary>
     /// <typeparam name="TGroupValue"></typeparam>
-    public abstract class AbstractFirstPassGroupingCollector<TGroupValue> : Collector
+    public abstract class AbstractFirstPassGroupingCollector<TGroupValue> : Collector, IAbstractFirstPassGroupingCollector<TGroupValue>
     {
         private readonly Sort groupSort;
         private readonly FieldComparator[] comparators;
@@ -88,7 +88,7 @@ namespace Lucene.Net.Search.Grouping
         /// <param name="groupOffset">The offset in the collected groups</param>
         /// <param name="fillFields">Whether to fill to <see cref="SearchGroup.sortValues"/></param>
         /// <returns>top groups, starting from offset</returns>
-        public ICollection<SearchGroup<TGroupValue>> GetTopGroups(int groupOffset, bool fillFields)
+        public IEnumerable<ISearchGroup<TGroupValue>> GetTopGroups(int groupOffset, bool fillFields)
         {
 
             //System.out.println("FP.getTopGroups groupOffset=" + groupOffset + " fillFields=" + fillFields + " groupMap.size()=" + groupMap.size());
@@ -108,7 +108,7 @@ namespace Lucene.Net.Search.Grouping
                 BuildSortedSet();
             }
 
-            ICollection<SearchGroup<TGroupValue>> result = new List<SearchGroup<TGroupValue>>();
+            ICollection<ISearchGroup<TGroupValue>> result = new List<ISearchGroup<TGroupValue>>();
             int upto = 0;
             int sortFieldCount = groupSort.GetSort().Length;
             foreach (CollectedSearchGroup<TGroupValue> group in orderedGroups)
@@ -119,13 +119,13 @@ namespace Lucene.Net.Search.Grouping
                 }
                 //System.out.println("  group=" + (group.groupValue == null ? "null" : group.groupValue.utf8ToString()));
                 SearchGroup<TGroupValue> searchGroup = new SearchGroup<TGroupValue>();
-                searchGroup.groupValue = group.groupValue;
+                searchGroup.GroupValue = group.GroupValue;
                 if (fillFields)
                 {
-                    searchGroup.sortValues = new object[sortFieldCount];
+                    searchGroup.SortValues = new object[sortFieldCount];
                     for (int sortFieldIDX = 0; sortFieldIDX < sortFieldCount; sortFieldIDX++)
                     {
-                        searchGroup.sortValues[sortFieldIDX] = comparators[sortFieldIDX].Value(group.ComparatorSlot);
+                        searchGroup.SortValues[sortFieldIDX] = comparators[sortFieldIDX].Value(group.ComparatorSlot);
                     }
                 }
                 result.Add(searchGroup);
@@ -206,14 +206,14 @@ namespace Lucene.Net.Search.Grouping
 
                     // Add a new CollectedSearchGroup:
                     CollectedSearchGroup<TGroupValue> sg = new CollectedSearchGroup<TGroupValue>();
-                    sg.groupValue = CopyDocGroupValue(groupValue, default(TGroupValue));
+                    sg.GroupValue = CopyDocGroupValue(groupValue, default(TGroupValue));
                     sg.ComparatorSlot = groupMap.Count;
                     sg.TopDoc = docBase + doc;
                     foreach (FieldComparator fc in comparators)
                     {
                         fc.Copy(sg.ComparatorSlot, doc);
                     }
-                    groupMap[sg.groupValue] = sg;
+                    groupMap[sg.GroupValue] = sg;
 
                     if (groupMap.Count == topNGroups)
                     {
@@ -237,10 +237,10 @@ namespace Lucene.Net.Search.Grouping
                 }
                 Debug.Assert(orderedGroups.Count == topNGroups - 1);
 
-                groupMap.Remove(bottomGroup.groupValue);
+                groupMap.Remove(bottomGroup.GroupValue);
 
                 // reuse the removed CollectedSearchGroup
-                bottomGroup.groupValue = CopyDocGroupValue(groupValue, bottomGroup.groupValue);
+                bottomGroup.GroupValue = CopyDocGroupValue(groupValue, bottomGroup.GroupValue);
                 bottomGroup.TopDoc = docBase + doc;
 
                 foreach (FieldComparator fc in comparators)
@@ -248,7 +248,7 @@ namespace Lucene.Net.Search.Grouping
                     fc.Copy(bottomGroup.ComparatorSlot, doc);
                 }
 
-                groupMap[bottomGroup.groupValue] = bottomGroup;
+                groupMap[bottomGroup.GroupValue] = bottomGroup;
                 orderedGroups.Add(bottomGroup);
                 Debug.Assert(orderedGroups.Count == topNGroups);
 
@@ -424,4 +424,23 @@ namespace Lucene.Net.Search.Grouping
         protected abstract TGroupValue CopyDocGroupValue(TGroupValue groupValue, TGroupValue reuse);
 
     }
+
+    /// <summary>
+    /// LUCENENET specific interface used to apply covariance to TGroupValue
+    /// </summary>
+    /// <typeparam name="TGroupValue"></typeparam>
+    public interface IAbstractFirstPassGroupingCollector<out TGroupValue>
+    {
+        // LUCENENET NOTE: We must use IEnumerable rather than ICollection here because we need
+        // this to be covariant
+        /// <summary>
+        /// Returns top groups, starting from offset.  This may
+        /// return null, if no groups were collected, or if the
+        /// number of unique groups collected is &lt;= offset.
+        /// </summary>
+        /// <param name="groupOffset">The offset in the collected groups</param>
+        /// <param name="fillFields">Whether to fill to <see cref="SearchGroup.sortValues"/></param>
+        /// <returns>top groups, starting from offset</returns>
+        IEnumerable<ISearchGroup<TGroupValue>> GetTopGroups(int groupOffset, bool fillFields);
+    }
 }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/0a137bea/src/Lucene.Net.Grouping/AbstractSecondPassGroupingCollector.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Grouping/AbstractSecondPassGroupingCollector.cs b/src/Lucene.Net.Grouping/AbstractSecondPassGroupingCollector.cs
index 580617c..41b5837 100644
--- a/src/Lucene.Net.Grouping/AbstractSecondPassGroupingCollector.cs
+++ b/src/Lucene.Net.Grouping/AbstractSecondPassGroupingCollector.cs
@@ -26,19 +26,19 @@ namespace Lucene.Net.Search.Grouping
         protected readonly IDictionary<TGroupValue, AbstractSecondPassGroupingCollector.SearchGroupDocs<TGroupValue>> groupMap;
         private readonly int maxDocsPerGroup;
         protected AbstractSecondPassGroupingCollector.SearchGroupDocs<TGroupValue>[] groupDocs;
-        private readonly ICollection<SearchGroup<TGroupValue>> groups;
+        private readonly IEnumerable<SearchGroup<TGroupValue>> groups;
         private readonly Sort withinGroupSort;
         private readonly Sort groupSort;
 
         private int totalHitCount;
         private int totalGroupedHitCount;
 
-        public AbstractSecondPassGroupingCollector(ICollection<SearchGroup<TGroupValue>> groups, Sort groupSort, Sort withinGroupSort,
+        public AbstractSecondPassGroupingCollector(IEnumerable<SearchGroup<TGroupValue>> groups, Sort groupSort, Sort withinGroupSort,
                                                    int maxDocsPerGroup, bool getScores, bool getMaxScores, bool fillSortFields)
         {
 
             //System.out.println("SP init");
-            if (groups.Count == 0)
+            if (groups.Count() == 0)
             {
                 throw new ArgumentException("no groups to collect (groups.size() is 0)");
             }
@@ -47,7 +47,7 @@ namespace Lucene.Net.Search.Grouping
             this.withinGroupSort = withinGroupSort;
             this.groups = groups;
             this.maxDocsPerGroup = maxDocsPerGroup;
-            groupMap = new Dictionary<TGroupValue, AbstractSecondPassGroupingCollector.SearchGroupDocs<TGroupValue>>(groups.Count);
+            groupMap = new Dictionary<TGroupValue, AbstractSecondPassGroupingCollector.SearchGroupDocs<TGroupValue>>(groups.Count());
 
             foreach (SearchGroup<TGroupValue> group in groups)
             {
@@ -64,7 +64,7 @@ namespace Lucene.Net.Search.Grouping
                     // Sort by fields
                     collector = TopFieldCollector.Create(withinGroupSort, maxDocsPerGroup, fillSortFields, getScores, getMaxScores, true);
                 }
-                groupMap[group.groupValue] = new AbstractSecondPassGroupingCollector.SearchGroupDocs<TGroupValue>(group.groupValue, collector);
+                groupMap[group.GroupValue] = new AbstractSecondPassGroupingCollector.SearchGroupDocs<TGroupValue>(group.GroupValue, collector);
             }
         }
 
@@ -118,20 +118,20 @@ namespace Lucene.Net.Search.Grouping
 
         public TopGroups<TGroupValue> GetTopGroups(int withinGroupOffset)
         {
-            GroupDocs<TGroupValue>[] groupDocsResult = new GroupDocs<TGroupValue>[groups.Count];
+            GroupDocs<TGroupValue>[] groupDocsResult = new GroupDocs<TGroupValue>[groups.Count()];
 
             int groupIDX = 0;
             float maxScore = float.MinValue;
             foreach (var group in groups)
             {
-                AbstractSecondPassGroupingCollector.SearchGroupDocs<TGroupValue> groupDocs = groupMap.ContainsKey(group.groupValue) ? groupMap[group.groupValue] : null;
+                AbstractSecondPassGroupingCollector.SearchGroupDocs<TGroupValue> groupDocs = groupMap.ContainsKey(group.GroupValue) ? groupMap[group.GroupValue] : null;
                 TopDocs topDocs = groupDocs.collector.TopDocs(withinGroupOffset, maxDocsPerGroup);
                 groupDocsResult[groupIDX++] = new GroupDocs<TGroupValue>(float.NaN,
                                                                               topDocs.MaxScore,
                                                                               topDocs.TotalHits,
                                                                               topDocs.ScoreDocs,
                                                                               groupDocs.groupValue,
-                                                                              group.sortValues);
+                                                                              group.SortValues);
                 maxScore = Math.Max(maxScore, topDocs.MaxScore);
             }
 
@@ -167,4 +167,13 @@ namespace Lucene.Net.Search.Grouping
             }
         }
     }
+
+    /// <summary>
+    /// LUCENENET specific interface used to apply covariance to TGroupValue
+    /// </summary>
+    /// <typeparam name="TGroupValue"></typeparam>
+    public interface IAbstractSecondPassGroupingCollector<out TGroupValue>
+    {
+        ITopGroups<TGroupValue> GetTopGroups(int withinGroupOffset);
+    }
 }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/0a137bea/src/Lucene.Net.Grouping/Function/FunctionAllGroupsCollector.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Grouping/Function/FunctionAllGroupsCollector.cs b/src/Lucene.Net.Grouping/Function/FunctionAllGroupsCollector.cs
index 638f36b..c5c7623 100644
--- a/src/Lucene.Net.Grouping/Function/FunctionAllGroupsCollector.cs
+++ b/src/Lucene.Net.Grouping/Function/FunctionAllGroupsCollector.cs
@@ -39,7 +39,7 @@ namespace Lucene.Net.Search.Grouping.Function
             this.groupBy = groupBy;
         }
 
-        public override ICollection<MutableValue> Groups
+        public override IEnumerable<MutableValue> Groups
         {
             get
             {

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/0a137bea/src/Lucene.Net.Grouping/Function/FunctionDistinctValuesCollector.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Grouping/Function/FunctionDistinctValuesCollector.cs b/src/Lucene.Net.Grouping/Function/FunctionDistinctValuesCollector.cs
index 2e84920..3d5dc0a 100644
--- a/src/Lucene.Net.Grouping/Function/FunctionDistinctValuesCollector.cs
+++ b/src/Lucene.Net.Grouping/Function/FunctionDistinctValuesCollector.cs
@@ -36,7 +36,7 @@ namespace Lucene.Net.Search.Grouping.Function
             groupMap = new LurchTable<MutableValue, GroupCount>(1 << 4);
             foreach (SearchGroup<MutableValue> group in groups)
             {
-                groupMap[group.groupValue] = new GroupCount(group.groupValue);
+                groupMap[group.GroupValue] = new GroupCount(group.GroupValue);
             }
         }
 

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/0a137bea/src/Lucene.Net.Grouping/GroupDocs.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Grouping/GroupDocs.cs b/src/Lucene.Net.Grouping/GroupDocs.cs
index 29d227e..cc1a7c6 100644
--- a/src/Lucene.Net.Grouping/GroupDocs.cs
+++ b/src/Lucene.Net.Grouping/GroupDocs.cs
@@ -24,39 +24,39 @@ namespace Lucene.Net.Search.Grouping
     /// 
     /// @lucene.experimental 
     /// </summary>
-    public class GroupDocs<TGroupValue>
+    public class GroupDocs<TGroupValue> : IGroupDocs<TGroupValue>
     {
         /// <summary>
         /// The groupField value for all docs in this group; this
         /// may be null if hits did not have the groupField. 
         /// </summary>
-        public readonly TGroupValue GroupValue;
+        public TGroupValue GroupValue { get; private set; }
 
         /// <summary>
         /// Max score in this group
         /// </summary>
-        public readonly float MaxScore;
+        public float MaxScore { get; private set; }
 
         /// <summary>
         /// Overall aggregated score of this group (currently only set by join queries). 
         /// </summary>
-        public readonly float Score;
+        public float Score { get; private set; }
 
         /// <summary>
-        /// Hits; this may be {@link org.apache.lucene.search.FieldDoc} instances if the
+        /// Hits; this may be <see cref="FieldDoc"/> instances if the
         /// withinGroupSort sorted by fields. 
         /// </summary>
-        public readonly ScoreDoc[] ScoreDocs;
+        public ScoreDoc[] ScoreDocs { get; private set; }
 
         /// <summary>
         /// Total hits within this group
         /// </summary>
-        public readonly int TotalHits;
+        public int TotalHits { get; private set; }
 
         /// <summary>
-        /// Matches the groupSort passed to {@link AbstractFirstPassGroupingCollector}. 
+        /// Matches the groupSort passed to <see cref="AbstractFirstPassGroupingCollector{TGroupValue}"/>. 
         /// </summary>
-        public readonly object[] GroupSortValues;
+        public object[] GroupSortValues { get; private set; }
 
         public GroupDocs(float score, float maxScore, int totalHits, ScoreDoc[] scoreDocs, TGroupValue groupValue, object[] groupSortValues)
         {
@@ -68,4 +68,43 @@ namespace Lucene.Net.Search.Grouping
             GroupSortValues = groupSortValues;
         }
     }
+
+    /// <summary>
+    /// LUCENENET specific interface used to apply covariance to TGroupValue
+    /// </summary>
+    /// <typeparam name="TGroupValue"></typeparam>
+    public interface IGroupDocs<out TGroupValue>
+    {
+        /// <summary>
+        /// The groupField value for all docs in this group; this
+        /// may be null if hits did not have the groupField. 
+        /// </summary>
+        TGroupValue GroupValue { get; }
+
+        /// <summary>
+        /// Max score in this group
+        /// </summary>
+        float MaxScore { get; }
+
+        /// <summary>
+        /// Overall aggregated score of this group (currently only set by join queries). 
+        /// </summary>
+        float Score { get; }
+
+        /// <summary>
+        /// Hits; this may be <see cref="FieldDoc"/> instances if the
+        /// withinGroupSort sorted by fields. 
+        /// </summary>
+        ScoreDoc[] ScoreDocs { get; }
+
+        /// <summary>
+        /// Total hits within this group
+        /// </summary>
+        int TotalHits { get; }
+
+        /// <summary>
+        /// Matches the groupSort passed to <see cref="AbstractFirstPassGroupingCollector{TGroupValue}"/>. 
+        /// </summary>
+        object[] GroupSortValues { get; }
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/0a137bea/src/Lucene.Net.Grouping/GroupingSearch.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Grouping/GroupingSearch.cs b/src/Lucene.Net.Grouping/GroupingSearch.cs
index 28b5381..56554f6 100644
--- a/src/Lucene.Net.Grouping/GroupingSearch.cs
+++ b/src/Lucene.Net.Grouping/GroupingSearch.cs
@@ -41,7 +41,7 @@ namespace Lucene.Net.Search.Grouping
         private bool allGroupHeads;
         private int initialSize = 128;
 
-        private ICollection /* Collection<?> */ matchingGroups;
+        private IList /* Collection<?> */ matchingGroups;
         private Bits matchingGroupHeads;
 
         /**
@@ -97,7 +97,7 @@ namespace Lucene.Net.Search.Grouping
          * @return the grouped result as a {@link TopGroups} instance
          * @throws IOException If any I/O related errors occur
          */
-        public TopGroups<TGroupValue> Search<TGroupValue>(IndexSearcher searcher, Query query, int groupOffset, int groupLimit)
+        public ITopGroups<TGroupValue> Search<TGroupValue>(IndexSearcher searcher, Query query, int groupOffset, int groupLimit)
         {
             return Search<TGroupValue>(searcher, null, query, groupOffset, groupLimit);
         }
@@ -113,7 +113,7 @@ namespace Lucene.Net.Search.Grouping
          * @return the grouped result as a {@link TopGroups} instance
          * @throws IOException If any I/O related errors occur
          */
-        public TopGroups<TGroupValue> Search<TGroupValue>(IndexSearcher searcher, Filter filter, Query query, int groupOffset, int groupLimit)
+        public ITopGroups<TGroupValue> Search<TGroupValue>(IndexSearcher searcher, Filter filter, Query query, int groupOffset, int groupLimit)
         {
             if (groupField != null || groupFunction != null)
             {
@@ -129,147 +129,151 @@ namespace Lucene.Net.Search.Grouping
             }
         }
 
-        protected TopGroups<TGroupValue> GroupByFieldOrFunction<TGroupValue>(IndexSearcher searcher, Filter filter, Query query, int groupOffset, int groupLimit)
+        protected ITopGroups<TGroupValue> GroupByFieldOrFunction<TGroupValue>(IndexSearcher searcher, Filter filter, Query query, int groupOffset, int groupLimit)
         {
-            // LUCENENET TODO: Finish
-            return null;
-            //int topN = groupOffset + groupLimit;
-            //AbstractFirstPassGroupingCollector<TGroupValue> firstPassCollector;
-            //AbstractAllGroupsCollector<TGroupValue> allGroupsCollector;
-            //AbstractAllGroupHeadsCollector allGroupHeadsCollector;
-            //if (groupFunction != null)
-            //{
-            //    firstPassCollector = new FunctionFirstPassGroupingCollector(groupFunction, valueSourceContext, groupSort, topN);
-            //    if (allGroups)
-            //    {
-            //        allGroupsCollector = new FunctionAllGroupsCollector(groupFunction, valueSourceContext);
-            //    }
-            //    else
-            //    {
-            //        allGroupsCollector = null;
-            //    }
-            //    if (allGroupHeads)
-            //    {
-            //        allGroupHeadsCollector = new FunctionAllGroupHeadsCollector(groupFunction, valueSourceContext, sortWithinGroup);
-            //    }
-            //    else
-            //    {
-            //        allGroupHeadsCollector = null;
-            //    }
-            //}
-            //else
-            //{
-            //    firstPassCollector = new TermFirstPassGroupingCollector(groupField, groupSort, topN);
-            //    if (allGroups)
-            //    {
-            //        allGroupsCollector = new TermAllGroupsCollector(groupField, initialSize);
-            //    }
-            //    else
-            //    {
-            //        allGroupsCollector = null;
-            //    }
-            //    if (allGroupHeads)
-            //    {
-            //        allGroupHeadsCollector = TermAllGroupHeadsCollector.Create(groupField, sortWithinGroup, initialSize);
-            //    }
-            //    else
-            //    {
-            //        allGroupHeadsCollector = null;
-            //    }
-            //}
-
-            //Collector firstRound;
-            //if (allGroupHeads || allGroups)
-            //{
-            //    List<Collector> collectors = new List<Collector>();
-            //    collectors.Add(firstPassCollector);
-            //    if (allGroups)
-            //    {
-            //        collectors.Add(allGroupsCollector);
-            //    }
-            //    if (allGroupHeads)
-            //    {
-            //        collectors.Add(allGroupHeadsCollector);
-            //    }
-            //    firstRound = MultiCollector.Wrap(collectors.ToArray(/* new Collector[collectors.size()] */));
-            //}
-            //else
-            //{
-            //    firstRound = firstPassCollector;
-            //}
-
-            //CachingCollector cachedCollector = null;
-            //if (maxCacheRAMMB != null || maxDocsToCache != null)
-            //{
-            //    if (maxCacheRAMMB != null)
-            //    {
-            //        cachedCollector = CachingCollector.Create(firstRound, cacheScores, maxCacheRAMMB.Value);
-            //    }
-            //    else
-            //    {
-            //        cachedCollector = CachingCollector.Create(firstRound, cacheScores, maxDocsToCache.Value);
-            //    }
-            //    searcher.Search(query, filter, cachedCollector);
-            //}
-            //else
-            //{
-            //    searcher.Search(query, filter, firstRound);
-            //}
-
-            //if (allGroups)
-            //{
-            //    matchingGroups = (ICollection)allGroupsCollector.GetGroups();
-            //}
-            //else
-            //{
-            //    matchingGroups = (ICollection)Collections.EmptyList<TGroupValue>();
-            //}
-            //if (allGroupHeads)
-            //{
-            //    matchingGroupHeads = allGroupHeadsCollector.RetrieveGroupHeads(searcher.IndexReader.MaxDoc);
-            //}
-            //else
-            //{
-            //    matchingGroupHeads = new Bits_MatchNoBits(searcher.IndexReader.MaxDoc);
-            //}
-
-            //ICollection<SearchGroup<TGroupValue>> topSearchGroups = firstPassCollector.GetTopGroups(groupOffset, fillSortFields);
-            //if (topSearchGroups == null)
-            //{
-            //    return new TopGroups<TGroupValue>(new SortField[0], new SortField[0], 0, 0, new GroupDocs<TGroupValue>[0], float.NaN);
-            //}
-
-            //int topNInsideGroup = groupDocsOffset + groupDocsLimit;
-            //AbstractSecondPassGroupingCollector<TGroupValue> secondPassCollector;
-            //if (groupFunction != null)
-            //{
-            //    secondPassCollector = new FunctionSecondPassGroupingCollector(topSearchGroups as ICollection<SearchGroup<MutableValue>>, groupSort, sortWithinGroup, topNInsideGroup, includeScores, includeMaxScore, fillSortFields, groupFunction, valueSourceContext);
-            //}
-            //else
-            //{
-            //    secondPassCollector = new TermSecondPassGroupingCollector(groupField, topSearchGroups as ICollection<SearchGroup<BytesRef>>, groupSort, sortWithinGroup, topNInsideGroup, includeScores, includeMaxScore, fillSortFields);
-            //}
-
-            //if (cachedCollector != null && cachedCollector.Cached)
-            //{
-            //    cachedCollector.Replay(secondPassCollector);
-            //}
-            //else
-            //{
-            //    searcher.Search(query, filter, secondPassCollector);
-            //}
-
-            //if (allGroups)
-            //{
-            //    return new TopGroups<TGroupValue>(secondPassCollector.GetTopGroups(groupDocsOffset), matchingGroups.Count);
-            //}
-            //else
-            //{
-            //    return secondPassCollector.GetTopGroups(groupDocsOffset);
-            //}
+            int topN = groupOffset + groupLimit;
+            IAbstractFirstPassGroupingCollector<TGroupValue> firstPassCollector;
+            IAbstractAllGroupsCollector<TGroupValue> allGroupsCollector;
+            AbstractAllGroupHeadsCollector allGroupHeadsCollector;
+            if (groupFunction != null)
+            {
+                firstPassCollector = (IAbstractFirstPassGroupingCollector<TGroupValue>)new FunctionFirstPassGroupingCollector(groupFunction, valueSourceContext, groupSort, topN);
+                if (allGroups)
+                {
+                    allGroupsCollector = (IAbstractAllGroupsCollector<TGroupValue>)new FunctionAllGroupsCollector(groupFunction, valueSourceContext);
+                }
+                else
+                {
+                    allGroupsCollector = null;
+                }
+                if (allGroupHeads)
+                {
+                    allGroupHeadsCollector = new FunctionAllGroupHeadsCollector(groupFunction, valueSourceContext, sortWithinGroup);
+                }
+                else
+                {
+                    allGroupHeadsCollector = null;
+                }
+            }
+            else
+            {
+                firstPassCollector = (IAbstractFirstPassGroupingCollector<TGroupValue>)new TermFirstPassGroupingCollector(groupField, groupSort, topN);
+                if (allGroups)
+                {
+                    allGroupsCollector = (IAbstractAllGroupsCollector<TGroupValue>)new TermAllGroupsCollector(groupField, initialSize);
+                }
+                else
+                {
+                    allGroupsCollector = null;
+                }
+                if (allGroupHeads)
+                {
+                    allGroupHeadsCollector = TermAllGroupHeadsCollector.Create(groupField, sortWithinGroup, initialSize);
+                }
+                else
+                {
+                    allGroupHeadsCollector = null;
+                }
+            }
+
+            Collector firstRound;
+            if (allGroupHeads || allGroups)
+            {
+                List<Collector> collectors = new List<Collector>();
+                // LUCENENET TODO: Make the Collector abstract class into an interface
+                // so we can remove the casting here
+                collectors.Add((Collector)firstPassCollector);
+                if (allGroups)
+                {
+                    // LUCENENET TODO: Make the Collector abstract class into an interface
+                    // so we can remove the casting here
+                    collectors.Add((Collector)allGroupsCollector);
+                }
+                if (allGroupHeads)
+                {
+                    collectors.Add(allGroupHeadsCollector);
+                }
+                firstRound = MultiCollector.Wrap(collectors.ToArray(/* new Collector[collectors.size()] */));
+            }
+            else
+            {
+                // LUCENENET TODO: Make the Collector abstract class into an interface
+                // so we can remove the casting here
+                firstRound = (Collector)firstPassCollector;
+            }
+
+            CachingCollector cachedCollector = null;
+            if (maxCacheRAMMB != null || maxDocsToCache != null)
+            {
+                if (maxCacheRAMMB != null)
+                {
+                    cachedCollector = CachingCollector.Create(firstRound, cacheScores, maxCacheRAMMB.Value);
+                }
+                else
+                {
+                    cachedCollector = CachingCollector.Create(firstRound, cacheScores, maxDocsToCache.Value);
+                }
+                searcher.Search(query, filter, cachedCollector);
+            }
+            else
+            {
+                searcher.Search(query, filter, firstRound);
+            }
+
+            if (allGroups)
+            {
+                matchingGroups = (IList)allGroupsCollector.Groups;
+            }
+            else
+            {
+                matchingGroups = new List<TGroupValue>();
+            }
+            if (allGroupHeads)
+            {
+                matchingGroupHeads = allGroupHeadsCollector.RetrieveGroupHeads(searcher.IndexReader.MaxDoc);
+            }
+            else
+            {
+                matchingGroupHeads = new Bits_MatchNoBits(searcher.IndexReader.MaxDoc);
+            }
+
+            IEnumerable<ISearchGroup<TGroupValue>> topSearchGroups = firstPassCollector.GetTopGroups(groupOffset, fillSortFields);
+            if (topSearchGroups == null)
+            {
+                return new TopGroups<TGroupValue>(new SortField[0], new SortField[0], 0, 0, new GroupDocs<TGroupValue>[0], float.NaN);
+            }
+
+            int topNInsideGroup = groupDocsOffset + groupDocsLimit;
+            IAbstractSecondPassGroupingCollector<TGroupValue> secondPassCollector;
+            if (groupFunction != null)
+            {
+                secondPassCollector = (IAbstractSecondPassGroupingCollector<TGroupValue>)new FunctionSecondPassGroupingCollector(topSearchGroups as ICollection<SearchGroup<MutableValue>>, groupSort, sortWithinGroup, topNInsideGroup, includeScores, includeMaxScore, fillSortFields, groupFunction, valueSourceContext);
+            }
+            else
+            {
+                secondPassCollector = (IAbstractSecondPassGroupingCollector<TGroupValue>)new TermSecondPassGroupingCollector(groupField, topSearchGroups as ICollection<SearchGroup<BytesRef>>, groupSort, sortWithinGroup, topNInsideGroup, includeScores, includeMaxScore, fillSortFields);
+            }
+
+            if (cachedCollector != null && cachedCollector.Cached)
+            {
+                cachedCollector.Replay((Collector)secondPassCollector);
+            }
+            else
+            {
+                searcher.Search(query, filter, (Collector)secondPassCollector);
+            }
+
+            if (allGroups)
+            {
+                return new TopGroups<TGroupValue>(secondPassCollector.GetTopGroups(groupDocsOffset), matchingGroups.Count);
+            }
+            else
+            {
+                return secondPassCollector.GetTopGroups(groupDocsOffset);
+            }
         }
 
-        protected TopGroups<T> GroupByDocBlock<T>(IndexSearcher searcher, Filter filter, Query query, int groupOffset, int groupLimit)
+        protected ITopGroups<T> GroupByDocBlock<T>(IndexSearcher searcher, Filter filter, Query query, int groupOffset, int groupLimit)
         {
             int topN = groupOffset + groupLimit;
             BlockGroupingCollector c = new BlockGroupingCollector(groupSort, topN, includeScores, groupEndDocs);

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/0a137bea/src/Lucene.Net.Grouping/SearchGroup.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Grouping/SearchGroup.cs b/src/Lucene.Net.Grouping/SearchGroup.cs
index 6ab902f..693e513 100644
--- a/src/Lucene.Net.Grouping/SearchGroup.cs
+++ b/src/Lucene.Net.Grouping/SearchGroup.cs
@@ -14,21 +14,21 @@ namespace Lucene.Net.Search.Grouping
     /// @lucene.experimental
     /// </summary>
     /// <typeparam name="TGroupValue"></typeparam>
-    public class SearchGroup<TGroupValue>
+    public class SearchGroup<TGroupValue> : ISearchGroup<TGroupValue>
     {
         /** The value that defines this group  */
-        public TGroupValue groupValue;
+        public TGroupValue GroupValue { get; set; }
 
         /** The sort values used during sorting. These are the
          *  groupSort field values of the highest rank document
          *  (by the groupSort) within the group.  Can be
          * <code>null</code> if <code>fillFields=false</code> had
          * been passed to {@link AbstractFirstPassGroupingCollector#getTopGroups} */
-        public object[] sortValues;
+        public object[] SortValues { get; set; }
 
         public override string ToString()
         {
-            return ("SearchGroup(groupValue=" + groupValue + " sortValues=" + Arrays.ToString(sortValues) + ")");
+            return ("SearchGroup(groupValue=" + GroupValue + " sortValues=" + Arrays.ToString(SortValues) + ")");
         }
 
         public override bool Equals(object o)
@@ -38,14 +38,14 @@ namespace Lucene.Net.Search.Grouping
 
             SearchGroup<TGroupValue> that = (SearchGroup<TGroupValue>)o;
 
-            if (groupValue == null)
+            if (GroupValue == null)
             {
-                if (that.groupValue != null)
+                if (that.GroupValue != null)
                 {
                     return false;
                 }
             }
-            else if (!groupValue.Equals(that.groupValue))
+            else if (!GroupValue.Equals(that.GroupValue))
             {
                 return false;
             }
@@ -55,7 +55,7 @@ namespace Lucene.Net.Search.Grouping
 
         public override int GetHashCode()
         {
-            return groupValue != null ? groupValue.GetHashCode() : 0;
+            return GroupValue != null ? GroupValue.GetHashCode() : 0;
         }
 
         private class ShardIter<T>
@@ -74,7 +74,7 @@ namespace Lucene.Net.Search.Grouping
             {
                 Debug.Assert(iter.MoveNext());
                 SearchGroup<T> group = iter.Current;
-                if (group.sortValues == null)
+                if (group.SortValues == null)
                 {
                     throw new ArgumentException("group.sortValues is null; you must pass fillFields=true to the first pass collector");
                 }
@@ -224,7 +224,7 @@ namespace Lucene.Net.Search.Grouping
                 while (shard.iter.MoveNext())
                 {
                     SearchGroup<T> group = shard.Next();
-                    MergedGroup<T> mergedGroup = groupsSeen.ContainsKey(group.groupValue) ? groupsSeen[group.groupValue] : null;
+                    MergedGroup<T> mergedGroup = groupsSeen.ContainsKey(group.GroupValue) ? groupsSeen[group.GroupValue] : null;
                     bool isNew = mergedGroup == null;
                     //System.out.println("    next group=" + (group.groupValue == null ? "null" : ((BytesRef) group.groupValue).utf8ToString()) + " sort=" + Arrays.toString(group.sortValues));
 
@@ -232,11 +232,11 @@ namespace Lucene.Net.Search.Grouping
                     {
                         // Start a new group:
                         //System.out.println("      new");
-                        mergedGroup = new MergedGroup<T>(group.groupValue);
+                        mergedGroup = new MergedGroup<T>(group.GroupValue);
                         mergedGroup.minShardIndex = shard.shardIndex;
-                        Debug.Assert(group.sortValues != null);
-                        mergedGroup.topValues = group.sortValues;
-                        groupsSeen[group.groupValue] = mergedGroup;
+                        Debug.Assert(group.SortValues != null);
+                        mergedGroup.topValues = group.SortValues;
+                        groupsSeen[group.GroupValue] = mergedGroup;
                         mergedGroup.inQueue = true;
                         queue.Add(mergedGroup);
                     }
@@ -252,7 +252,7 @@ namespace Lucene.Net.Search.Grouping
                         bool competes = false;
                         for (int compIDX = 0; compIDX < groupComp.comparators.Length; compIDX++)
                         {
-                            int cmp = groupComp.reversed[compIDX] * groupComp.comparators[compIDX].CompareValues(group.sortValues[compIDX],
+                            int cmp = groupComp.reversed[compIDX] * groupComp.comparators[compIDX].CompareValues(group.SortValues[compIDX],
                                                                                                                        mergedGroup.topValues[compIDX]);
                             if (cmp < 0)
                             {
@@ -283,7 +283,7 @@ namespace Lucene.Net.Search.Grouping
                             {
                                 queue.Remove(mergedGroup);
                             }
-                            mergedGroup.topValues = group.sortValues;
+                            mergedGroup.topValues = group.SortValues;
                             mergedGroup.minShardIndex = shard.shardIndex;
                             queue.Add(mergedGroup);
                             mergedGroup.inQueue = true;
@@ -335,8 +335,8 @@ namespace Lucene.Net.Search.Grouping
                     if (count++ >= offset)
                     {
                         SearchGroup<T> newGroup = new SearchGroup<T>();
-                        newGroup.groupValue = group.groupValue;
-                        newGroup.sortValues = group.topValues;
+                        newGroup.GroupValue = group.groupValue;
+                        newGroup.SortValues = group.topValues;
                         newTopGroups.Add(newGroup);
                         if (newTopGroups.Count == topN)
                         {
@@ -385,4 +385,15 @@ namespace Lucene.Net.Search.Grouping
             }
         }
     }
+
+    /// <summary>
+    /// LUCENENET specific interface used to provide covariance
+    /// with the TGroupValue type
+    /// </summary>
+    /// <typeparam name="TGroupValue"></typeparam>
+    public interface ISearchGroup<out TGroupValue>
+    {
+        TGroupValue GroupValue { get; }
+        object[] SortValues { get; set; }
+    }
 }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/0a137bea/src/Lucene.Net.Grouping/Term/TermAllGroupsCollector.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Grouping/Term/TermAllGroupsCollector.cs b/src/Lucene.Net.Grouping/Term/TermAllGroupsCollector.cs
index 7693d93..ef33aa5 100644
--- a/src/Lucene.Net.Grouping/Term/TermAllGroupsCollector.cs
+++ b/src/Lucene.Net.Grouping/Term/TermAllGroupsCollector.cs
@@ -83,7 +83,7 @@ namespace Lucene.Net.Search.Grouping.Terms
             }
         }
 
-        public override ICollection<BytesRef> Groups
+        public override IEnumerable<BytesRef> Groups
         {
             get
             {

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/0a137bea/src/Lucene.Net.Grouping/Term/TermDistinctValuesCollector.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Grouping/Term/TermDistinctValuesCollector.cs b/src/Lucene.Net.Grouping/Term/TermDistinctValuesCollector.cs
index d6f6bab..502b0ea 100644
--- a/src/Lucene.Net.Grouping/Term/TermDistinctValuesCollector.cs
+++ b/src/Lucene.Net.Grouping/Term/TermDistinctValuesCollector.cs
@@ -41,7 +41,7 @@ namespace Lucene.Net.Search.Grouping.Terms
             this.groups = new List<GroupCount>(groups.Count);
             foreach (SearchGroup<BytesRef> group in groups)
             {
-                this.groups.Add(new GroupCount(group.groupValue));
+                this.groups.Add(new GroupCount(group.GroupValue));
             }
             ordSet = new SentinelIntSet(groups.Count, -2);
             groupCounts = new GroupCount[ordSet.Keys.Length];

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/0a137bea/src/Lucene.Net.Grouping/TopGroups.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Grouping/TopGroups.cs b/src/Lucene.Net.Grouping/TopGroups.cs
index 091103d..b5d5c65 100644
--- a/src/Lucene.Net.Grouping/TopGroups.cs
+++ b/src/Lucene.Net.Grouping/TopGroups.cs
@@ -24,39 +24,39 @@ namespace Lucene.Net.Search.Grouping
     /// 
     /// @lucene.experimental 
     /// </summary>
-    public class TopGroups<TGroupValue>
+    public class TopGroups<TGroupValue> : ITopGroups<TGroupValue>
     {
         /// <summary>
         /// Number of documents matching the search </summary>
-        public readonly int TotalHitCount;
+        public int TotalHitCount { get; private set; }
 
         /// <summary>
         /// Number of documents grouped into the topN groups </summary>
-        public readonly int TotalGroupedHitCount;
+        public int TotalGroupedHitCount { get; private set; }
 
         /// <summary>
-        /// The total number of unique groups. If <code>null</code> this value is not computed. </summary>
-        public readonly int? TotalGroupCount;
+        /// The total number of unique groups. If <c>null</c> this value is not computed. </summary>
+        public int? TotalGroupCount { get; private set; }
 
         /// <summary>
         /// Group results in groupSort order </summary>
-        public readonly GroupDocs<TGroupValue>[] Groups;
+        public IGroupDocs<TGroupValue>[] Groups { get; private set; }
 
         /// <summary>
         /// How groups are sorted against each other </summary>
-        public readonly SortField[] GroupSort;
+        public SortField[] GroupSort { get; private set; }
 
         /// <summary>
         /// How docs are sorted within each group </summary>
-        public readonly SortField[] WithinGroupSort;
+        public SortField[] WithinGroupSort { get; private set; }
 
         /// <summary>
         /// Highest score across all hits, or
-        ///  <code>Float.NaN</code> if scores were not computed. 
+        /// <see cref="float.NaN"/> if scores were not computed. 
         /// </summary>
-        public readonly float MaxScore;
+        public float MaxScore { get; private set; }
 
-        public TopGroups(SortField[] groupSort, SortField[] withinGroupSort, int totalHitCount, int totalGroupedHitCount, GroupDocs<TGroupValue>[] groups, float maxScore)
+        public TopGroups(SortField[] groupSort, SortField[] withinGroupSort, int totalHitCount, int totalGroupedHitCount, IGroupDocs<TGroupValue>[] groups, float maxScore)
         {
             GroupSort = groupSort;
             WithinGroupSort = withinGroupSort;
@@ -67,7 +67,7 @@ namespace Lucene.Net.Search.Grouping
             MaxScore = maxScore;
         }
 
-        public TopGroups(TopGroups<TGroupValue> oldTopGroups, int? totalGroupCount)
+        public TopGroups(ITopGroups<TGroupValue> oldTopGroups, int? totalGroupCount)
         {
             GroupSort = oldTopGroups.GroupSort;
             WithinGroupSort = oldTopGroups.WithinGroupSort;
@@ -245,4 +245,42 @@ namespace Lucene.Net.Search.Grouping
             return new TopGroups<T>(groupSort.GetSort(), docSort == null ? null : docSort.GetSort(), totalHitCount, totalGroupedHitCount, mergedGroupDocs, totalMaxScore);
         }
     }
+
+    /// <summary>
+    /// LUCENENET specific interface used to provide covariance
+    /// with the TGroupValue type
+    /// </summary>
+    /// <typeparam name="TGroupValue"></typeparam>
+    public interface ITopGroups<out TGroupValue>
+    {
+        /// <summary>
+        /// Number of documents matching the search </summary>
+        int TotalHitCount { get; }
+
+        /// <summary>
+        /// Number of documents grouped into the topN groups </summary>
+        int TotalGroupedHitCount { get; }
+
+        /// <summary>
+        /// The total number of unique groups. If <c>null</c> this value is not computed. </summary>
+        int? TotalGroupCount { get; }
+
+        /// <summary>
+        /// Group results in groupSort order </summary>
+        IGroupDocs<TGroupValue>[] Groups { get; }
+
+        /// <summary>
+        /// How groups are sorted against each other </summary>
+        SortField[] GroupSort { get; }
+
+        /// <summary>
+        /// How docs are sorted within each group </summary>
+        SortField[] WithinGroupSort { get; }
+
+        /// <summary>
+        /// Highest score across all hits, or
+        /// <see cref="float.NaN"/> if scores were not computed. 
+        /// </summary>
+        float MaxScore { get; }
+    }
 }
\ No newline at end of file


Mime
View raw message