You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucenenet.apache.org by sy...@apache.org on 2014/11/14 12:59:16 UTC

[01/26] lucenenet git commit: Revert "Updating Lucene.Net.Codes/Pulsing"

Repository: lucenenet
Updated Branches:
  refs/heads/master 58ccdea29 -> 31a6e4fe5


Revert "Updating Lucene.Net.Codes/Pulsing"

This reverts commit 6e900565dc22706fd946c01c024ec96ca82f2ed1.


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

Branch: refs/heads/master
Commit: ce4970c8b6beb9b3fff8797a2bcaada4a930269d
Parents: 6e90056
Author: eladmarg <el...@gmail.com>
Authored: Fri Nov 7 18:33:16 2014 -0800
Committer: eladmarg <el...@gmail.com>
Committed: Fri Nov 7 18:33:16 2014 -0800

----------------------------------------------------------------------
 .../Pulsing/Pulsing41PostingsFormat.cs          |   2 +-
 .../Pulsing/PulsingPostingsFormat.cs            |  10 +-
 .../Pulsing/PulsingPostingsReader.cs            | 486 ++++++++++---------
 .../Pulsing/PulsingPostingsWriter.cs            | 319 ++++++------
 src/Lucene.Net.Core/Codecs/PostingsConsumer.cs  |   2 +-
 5 files changed, 435 insertions(+), 384 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucenenet/blob/ce4970c8/src/Lucene.Net.Codecs/Pulsing/Pulsing41PostingsFormat.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Codecs/Pulsing/Pulsing41PostingsFormat.cs b/src/Lucene.Net.Codecs/Pulsing/Pulsing41PostingsFormat.cs
index 458771e..f2634f4 100644
--- a/src/Lucene.Net.Codecs/Pulsing/Pulsing41PostingsFormat.cs
+++ b/src/Lucene.Net.Codecs/Pulsing/Pulsing41PostingsFormat.cs
@@ -17,7 +17,7 @@
 
 namespace Lucene.Net.Codecs.Pulsing
 {
-    using Lucene41;
+    using Lucene.Net.Codecs.Lucene41;
 
     /// <summary>
     /// Concrete pulsing implementation over {@link Lucene41PostingsFormat}.

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/ce4970c8/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsFormat.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsFormat.cs b/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsFormat.cs
index 9ab1706..552ecbc 100644
--- a/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsFormat.cs
+++ b/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsFormat.cs
@@ -15,12 +15,14 @@
  * limitations under the License.
  */
 
+
+
 namespace Lucene.Net.Codecs.Pulsing
 {
     using System;
     using System.Diagnostics;
-    using Index;
-    using Util;
+    using Lucene.Net.Index;
+    using Lucene.Net.Util;
 
     /// <summary>
     /// This postings format "inlines" the postings for terms that have
@@ -47,11 +49,11 @@ namespace Lucene.Net.Codecs.Pulsing
         {
         }
 
-        /// <summary>Terms with freq less than or equal freqCutoff are inlined into terms dict.</summary>
+        /// <summary>Terms with freq <= freqCutoff are inlined into terms dict.</summary>
         protected PulsingPostingsFormat(String name, PostingsBaseFormat wrappedPostingsBaseFormat, int freqCutoff,
             int minBlockSize, int maxBlockSize) : base(name)
         {
-            Debug.Assert(minBlockSize > 1);
+            Debug.Debug.Assert((minBlockSize > 1);
 
             _freqCutoff = freqCutoff;
             _minBlockSize = minBlockSize;

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/ce4970c8/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsReader.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsReader.cs b/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsReader.cs
index 75334e6..9f4599b 100644
--- a/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsReader.cs
+++ b/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsReader.cs
@@ -21,9 +21,9 @@ namespace Lucene.Net.Codecs.Pulsing
     using System;
     using System.Collections.Generic;
     using System.Diagnostics;
-    using Index;
-    using Store;
-    using Util;
+    using Lucene.Net.Index;
+    using Lucene.Net.Store;
+    using Lucene.Net.Util;
 
     /// <summary>
     /// Concrete class that reads the current doc/freq/skip postings format 
@@ -39,52 +39,52 @@ namespace Lucene.Net.Codecs.Pulsing
 
         // Fallback reader for non-pulsed terms:
         private readonly PostingsReaderBase _wrappedPostingsReader;
-        private readonly SegmentReadState _segmentState;
-        private int _maxPositions;
-        private int _version;
-        private SortedDictionary<int, int> _fields;
+        private readonly SegmentReadState segmentState;
+        private int maxPositions;
+        private int version;
+        private SortedDictionary<int, int> fields;
 
         public PulsingPostingsReader(SegmentReadState state, PostingsReaderBase wrappedPostingsReader)
         {
-            _wrappedPostingsReader = wrappedPostingsReader;
-            _segmentState = state;
+            this._wrappedPostingsReader = wrappedPostingsReader;
+            this.segmentState = state;
         }
 
         public override void Init(IndexInput termsIn)
         {
-            _version = CodecUtil.CheckHeader(termsIn, PulsingPostingsWriter.CODEC,
+            version = CodecUtil.CheckHeader(termsIn, PulsingPostingsWriter.CODEC,
                 PulsingPostingsWriter.VERSION_START,
                 PulsingPostingsWriter.VERSION_CURRENT);
 
-            _maxPositions = termsIn.ReadVInt();
+            maxPositions = termsIn.ReadVInt();
             _wrappedPostingsReader.Init(termsIn);
 
-            if (_wrappedPostingsReader is PulsingPostingsReader || _version < PulsingPostingsWriter.VERSION_META_ARRAY)
+            if (_wrappedPostingsReader is PulsingPostingsReader || version < PulsingPostingsWriter.VERSION_META_ARRAY)
             {
-                _fields = null;
+                fields = null;
             }
             else
             {
-                _fields = new SortedDictionary<int, int>();
-                var summaryFileName = IndexFileNames.SegmentFileName(_segmentState.SegmentInfo.Name,
-                    _segmentState.SegmentSuffix, PulsingPostingsWriter.SUMMARY_EXTENSION);
+                fields = new SortedDictionary<int, int>();
+                String summaryFileName = IndexFileNames.SegmentFileName(segmentState.SegmentInfo.Name,
+                    segmentState.SegmentSuffix, PulsingPostingsWriter.SUMMARY_EXTENSION);
                 IndexInput input = null;
 
                 try
                 {
                     input =
-                        _segmentState.Directory.OpenInput(summaryFileName, _segmentState.Context);
+                        segmentState.Directory.OpenInput(summaryFileName, segmentState.Context);
                     CodecUtil.CheckHeader(input,
                         PulsingPostingsWriter.CODEC,
-                        _version,
+                        version,
                         PulsingPostingsWriter.VERSION_CURRENT);
 
-                    var numField = input.ReadVInt();
-                    for (var i = 0; i < numField; i++)
+                    int numField = input.ReadVInt();
+                    for (int i = 0; i < numField; i++)
                     {
-                        var fieldNum = input.ReadVInt();
-                        var longsSize = input.ReadVInt();
-                        _fields.Add(fieldNum, longsSize);
+                        int fieldNum = input.ReadVInt();
+                        int longsSize = input.ReadVInt();
+                        fields.Add(fieldNum, longsSize);
                     }
                 }
                 finally
@@ -96,24 +96,25 @@ namespace Lucene.Net.Codecs.Pulsing
 
         public override BlockTermState NewTermState()
         {
-            return new PulsingTermState {WrappedTermState = _wrappedPostingsReader.NewTermState()};
+            var state = new PulsingTermState {WrappedTermState = _wrappedPostingsReader.NewTermState()};
+            return state;
         }
 
         public override void DecodeTerm(long[] empty, DataInput input, FieldInfo fieldInfo, BlockTermState _termState,
             bool absolute)
         {
-            var termState = (PulsingTermState) _termState;
-
-            Debug.Assert(empty.Length == 0);
+            PulsingTermState termState = (PulsingTermState) _termState;
 
+            Debug.Debug.Assert((empty.Length == 0);
             termState.Absolute = termState.Absolute || absolute;
             // if we have positions, its total TF, otherwise its computed based on docFreq.
             // TODO Double check this is right..
-            long count = FieldInfo.IndexOptions.DOCS_AND_FREQS_AND_POSITIONS.CompareTo(fieldInfo.FieldIndexOptions) <= 0
+            long count = FieldInfo.IndexOptions_e.DOCS_AND_FREQS_AND_POSITIONS.CompareTo(fieldInfo.IndexOptions) <= 0
                 ? termState.TotalTermFreq
                 : termState.DocFreq;
-           
-            if (count <= _maxPositions)
+            //System.out.println("  count=" + count + " threshold=" + maxPositions);
+
+            if (count <= maxPositions)
             {
                 // Inlined into terms dict -- just read the byte[] blob in,
                 // but don't decode it now (we only decode when a DocsEnum
@@ -133,12 +134,12 @@ namespace Lucene.Net.Codecs.Pulsing
             }
             else
             {
-                var longsSize = _fields == null ? 0 : _fields[fieldInfo.Number];
+                int longsSize = fields == null ? 0 : fields[fieldInfo.Number];
                 if (termState.Longs == null)
                 {
                     termState.Longs = new long[longsSize];
                 }
-                for (var i = 0; i < longsSize; i++)
+                for (int i = 0; i < longsSize; i++)
                 {
                     termState.Longs[i] = input.ReadVLong();
                 }
@@ -155,7 +156,7 @@ namespace Lucene.Net.Codecs.Pulsing
         public override DocsEnum Docs(FieldInfo field, BlockTermState _termState, Bits liveDocs, DocsEnum reuse,
             int flags)
         {
-            var termState = (PulsingTermState) _termState;
+            PulsingTermState termState = (PulsingTermState) _termState;
             if (termState.PostingsSize != -1)
             {
                 PulsingDocsEnum postings;
@@ -170,7 +171,7 @@ namespace Lucene.Net.Codecs.Pulsing
                 else
                 {
                     // the 'reuse' is actually the wrapped enum
-                    var previous = (PulsingDocsEnum) GetOther(reuse);
+                    PulsingDocsEnum previous = (PulsingDocsEnum) GetOther(reuse);
                     if (previous != null && previous.CanReuse(field))
                     {
                         postings = previous;
@@ -180,21 +181,26 @@ namespace Lucene.Net.Codecs.Pulsing
                         postings = new PulsingDocsEnum(field);
                     }
                 }
-                
                 if (reuse != postings)
+                {
                     SetOther(postings, reuse); // postings.other = reuse
-                
+                }
                 return postings.Reset(liveDocs, termState);
             }
-
-            if (!(reuse is PulsingDocsEnum))
-                return _wrappedPostingsReader.Docs(field, termState.WrappedTermState, liveDocs, reuse, flags);
-
-            var wrapped = _wrappedPostingsReader.Docs(field, termState.WrappedTermState, liveDocs,
-                GetOther(reuse), flags);
-
-            SetOther(wrapped, reuse); // wrapped.other = reuse
-            return wrapped;
+            else
+            {
+                if (reuse is PulsingDocsEnum)
+                {
+                    DocsEnum wrapped = _wrappedPostingsReader.Docs(field, termState.WrappedTermState, liveDocs,
+                        GetOther(reuse), flags);
+                    SetOther(wrapped, reuse); // wrapped.other = reuse
+                    return wrapped;
+                }
+                else
+                {
+                    return _wrappedPostingsReader.Docs(field, termState.WrappedTermState, liveDocs, reuse, flags);
+                }
+            }
         }
 
         public override DocsAndPositionsEnum DocsAndPositions(FieldInfo field, BlockTermState _termState, Bits liveDocs,
@@ -202,7 +208,7 @@ namespace Lucene.Net.Codecs.Pulsing
             int flags)
         {
 
-            var termState = (PulsingTermState) _termState;
+            PulsingTermState termState = (PulsingTermState) _termState;
 
             if (termState.PostingsSize != -1)
             {
@@ -218,7 +224,7 @@ namespace Lucene.Net.Codecs.Pulsing
                 else
                 {
                     // the 'reuse' is actually the wrapped enum
-                    var previous = (PulsingDocsAndPositionsEnum) GetOther(reuse);
+                    PulsingDocsAndPositionsEnum previous = (PulsingDocsAndPositionsEnum) GetOther(reuse);
                     if (previous != null && previous.CanReuse(field))
                     {
                         postings = previous;
@@ -232,19 +238,25 @@ namespace Lucene.Net.Codecs.Pulsing
                 {
                     SetOther(postings, reuse); // postings.other = reuse 
                 }
-                return postings.Reset(liveDocs, termState);
+                return postings.reset(liveDocs, termState);
+            }
+            else
+            {
+                if (reuse is PulsingDocsAndPositionsEnum)
+                {
+                    DocsAndPositionsEnum wrapped = _wrappedPostingsReader.DocsAndPositions(field,
+                        termState.WrappedTermState,
+                        liveDocs, (DocsAndPositionsEnum) GetOther(reuse),
+                        flags);
+                    SetOther(wrapped, reuse); // wrapped.other = reuse
+                    return wrapped;
+                }
+                else
+                {
+                    return _wrappedPostingsReader.DocsAndPositions(field, termState.WrappedTermState, liveDocs, reuse,
+                        flags);
+                }
             }
-
-            if (!(reuse is PulsingDocsAndPositionsEnum))
-                return _wrappedPostingsReader.DocsAndPositions(field, termState.WrappedTermState, liveDocs, reuse,
-                    flags);
-
-            var wrapped = _wrappedPostingsReader.DocsAndPositions(field,
-                termState.WrappedTermState,
-                liveDocs, (DocsAndPositionsEnum) GetOther(reuse),
-                flags);
-            SetOther(wrapped, reuse); // wrapped.other = reuse
-            return wrapped;
         }
 
         public override long RamBytesUsed()
@@ -275,10 +287,14 @@ namespace Lucene.Net.Codecs.Pulsing
         private DocsEnum GetOther(DocsEnum de)
         {
             if (de == null)
+            {
                 return null;
-            
-            var atts = de.Attributes();
-            return atts.AddAttribute(PulsingEnumAttribute.class).Enums().get(this);
+            }
+            else
+            {
+                AttributeSource atts = de.Attributes();
+                return atts.AddAttribute(PulsingEnumAttribute.Enums().get(this);
+            }
         }
 
         /// <summary>
@@ -287,8 +303,8 @@ namespace Lucene.Net.Codecs.Pulsing
         /// </summary>
         private DocsEnum SetOther(DocsEnum de, DocsEnum other)
         {
-            var atts = de.Attributes();
-            return atts.AddAttribute(PulsingEnumAttribute.class).Enums().put(this, other);
+            AttributeSource atts = de.Attributes();
+            return atts.AddAttribute(PulsingEnumAttributeImpl.Enums().put(this, other));
         }
 
         ///<summary>
@@ -313,7 +329,7 @@ namespace Lucene.Net.Codecs.Pulsing
 
             public override object Clone()
             {
-                var clone = (PulsingTermState) base.Clone();
+                PulsingTermState clone = (PulsingTermState) base.Clone();
                 if (PostingsSize != -1)
                 {
                     clone.Postings = new byte[PostingsSize];
@@ -321,14 +337,14 @@ namespace Lucene.Net.Codecs.Pulsing
                 }
                 else
                 {
-                    Debug.Assert(WrappedTermState != null);
+                    Debug.Debug.Assert((WrappedTermState != null);
                     clone.WrappedTermState = (BlockTermState) WrappedTermState.Clone();
                     clone.Absolute = Absolute;
-                    
-                    if (Longs == null) return clone;
-
-                    clone.Longs = new long[Longs.Length];
-                    Array.Copy(Longs, 0, clone.Longs, 0, Longs.Length);
+                    if (Longs != null)
+                    {
+                        clone.Longs = new long[Longs.Length];
+                        Array.Copy(Longs, 0, clone.Longs, 0, Longs.Length);
+                    }
                 }
                 return clone;
             }
@@ -344,7 +360,7 @@ namespace Lucene.Net.Codecs.Pulsing
                     {
                         Postings = new byte[ArrayUtil.Oversize(_other.PostingsSize, 1)];
                     }
-                    Array.Copy(_other.Postings, 0, Postings, 0, _other.PostingsSize);
+                    System.Array.Copy(_other.Postings, 0, Postings, 0, _other.PostingsSize);
                 }
                 else
                 {
@@ -355,341 +371,373 @@ namespace Lucene.Net.Codecs.Pulsing
             public override String ToString()
             {
                 if (PostingsSize == -1)
+                {
                     return "PulsingTermState: not inlined: wrapped=" + WrappedTermState;
-                
-                return "PulsingTermState: inlined size=" + PostingsSize + " " + base.ToString();
+                }
+                else
+                {
+                    return "PulsingTermState: inlined size=" + PostingsSize + " " + base.ToString();
+                }
             }
         }
 
         internal class PulsingDocsEnum : DocsEnum
         {
-            private byte[] _postingsBytes;
-            private readonly ByteArrayDataInput _postings = new ByteArrayDataInput();
-            private readonly FieldInfo.IndexOptions? _indexOptions;
-            private readonly bool _storePayloads;
-            private readonly bool _storeOffsets;
-            private Bits _liveDocs;
-
-            private int _docId = -1;
-            private int _accum;
-            private int _freq;
-            private int _payloadLength;
-            private int _cost;
+            private byte[] postingsBytes;
+            private readonly ByteArrayDataInput postings = new ByteArrayDataInput();
+            private readonly FieldInfo.IndexOptions_e? indexOptions;
+            private readonly bool storePayloads;
+            private readonly bool storeOffsets;
+            private Bits liveDocs;
+
+            private int docID = -1;
+            private int accum;
+            private int freq;
+            private int payloadLength;
+            private int cost;
 
             public PulsingDocsEnum(FieldInfo fieldInfo)
             {
-                _indexOptions = fieldInfo.FieldIndexOptions;
-                _storePayloads = fieldInfo.HasPayloads();
-                _storeOffsets = _indexOptions.Value.CompareTo(FieldInfo.IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS) >= 0;
+                indexOptions = fieldInfo.IndexOptions;
+                storePayloads = fieldInfo.HasPayloads();
+                storeOffsets = indexOptions.Value.CompareTo(FieldInfo.IndexOptions_e.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS) >= 0;
             }
 
             public PulsingDocsEnum Reset(Bits liveDocs, PulsingTermState termState)
             {
-                Debug.Assert(termState.PostingsSize != -1);
+                Debug.Debug.Assert((termState.PostingsSize != -1);
 
                 // Must make a copy of termState's byte[] so that if
                 // app does TermsEnum.next(), this DocsEnum is not affected
-                if (_postingsBytes == null)
+                if (postingsBytes == null)
                 {
-                    _postingsBytes = new byte[termState.PostingsSize];
+                    postingsBytes = new byte[termState.PostingsSize];
                 }
-                else if (_postingsBytes.Length < termState.PostingsSize)
+                else if (postingsBytes.Length < termState.PostingsSize)
                 {
-                    _postingsBytes = ArrayUtil.Grow(_postingsBytes, termState.PostingsSize);
+                    postingsBytes = ArrayUtil.Grow(postingsBytes, termState.PostingsSize);
                 }
-                System.Array.Copy(termState.Postings, 0, _postingsBytes, 0, termState.PostingsSize);
-                _postings.Reset(_postingsBytes, 0, termState.PostingsSize);
-                _docId = -1;
-                _accum = 0;
-                _freq = 1;
-                _cost = termState.DocFreq;
-                _payloadLength = 0;
-                this._liveDocs = liveDocs;
+                System.Array.Copy(termState.Postings, 0, postingsBytes, 0, termState.PostingsSize);
+                postings.Reset(postingsBytes, 0, termState.PostingsSize);
+                docID = -1;
+                accum = 0;
+                freq = 1;
+                cost = termState.DocFreq;
+                payloadLength = 0;
+                this.liveDocs = liveDocs;
                 return this;
             }
 
             public bool CanReuse(FieldInfo fieldInfo)
             {
-                return _indexOptions == fieldInfo.FieldIndexOptions && _storePayloads == fieldInfo.HasPayloads();
+                return indexOptions == fieldInfo.IndexOptions && storePayloads == fieldInfo.HasPayloads();
             }
 
             public override int DocID()
             {
-                return _docId;
+                return docID;
             }
 
             public override int NextDoc()
             {
+                //System.out.println("PR nextDoc this= "+ this);
                 while (true)
                 {
-                    if (_postings.Eof())
-                        return _docId = NO_MORE_DOCS;
-                    
-                    var code = _postings.ReadVInt();
-                    if (_indexOptions == FieldInfo.IndexOptions.DOCS_ONLY)
+                    if (postings.Eof())
                     {
-                        _accum += code;
+                        return docID = NO_MORE_DOCS;
+                    }
+
+                    int code = postings.ReadVInt();
+                    if (indexOptions == FieldInfo.IndexOptions_e.DOCS_ONLY)
+                    {
+                        accum += code;
                     }
                     else
                     {
-                        _accum += (int)((uint)code >> 1); ; // shift off low bit
-                        _freq = (code & 1) != 0 ? 1 : _postings.ReadVInt();
+                        accum += (int)((uint)code >> 1); ; // shift off low bit
+                        if ((code & 1) != 0)
+                        {
+                            // if low bit is set
+                            freq = 1; // freq is one
+                        }
+                        else
+                        {
+                            freq = postings.ReadVInt(); // else read freq
+                        }
 
-                        if (_indexOptions.Value.CompareTo(FieldInfo.IndexOptions.DOCS_AND_FREQS_AND_POSITIONS) >= 0)
+                        if (indexOptions.Value.CompareTo(FieldInfo.IndexOptions_e.DOCS_AND_FREQS_AND_POSITIONS) >= 0)
                         {
                             // Skip positions
-                            if (_storePayloads)
+                            if (storePayloads)
                             {
-                                for (var pos = 0; pos < _freq; pos++)
+                                for (int pos = 0; pos < freq; pos++)
                                 {
-                                    var posCode = _postings.ReadVInt();
+                                    int posCode = postings.ReadVInt();
                                     if ((posCode & 1) != 0)
                                     {
-                                        _payloadLength = _postings.ReadVInt();
+                                        payloadLength = postings.ReadVInt();
                                     }
-                                    if (_storeOffsets && (_postings.ReadVInt() & 1) != 0)
+                                    if (storeOffsets && (postings.ReadVInt() & 1) != 0)
                                     {
                                         // new offset length
-                                        _postings.ReadVInt();
+                                        postings.ReadVInt();
                                     }
-                                    if (_payloadLength != 0)
+                                    if (payloadLength != 0)
                                     {
-                                        _postings.SkipBytes(_payloadLength);
+                                        postings.SkipBytes(payloadLength);
                                     }
                                 }
                             }
                             else
                             {
-                                for (var pos = 0; pos < _freq; pos++)
+                                for (int pos = 0; pos < freq; pos++)
                                 {
                                     // TODO: skipVInt
-                                    _postings.ReadVInt();
-                                    if (_storeOffsets && (_postings.ReadVInt() & 1) != 0)
+                                    postings.ReadVInt();
+                                    if (storeOffsets && (postings.ReadVInt() & 1) != 0)
                                     {
                                         // new offset length
-                                        _postings.ReadVInt();
+                                        postings.ReadVInt();
                                     }
                                 }
                             }
                         }
                     }
 
-                    if (_liveDocs == null || _liveDocs.Get(_accum))
-                        return (_docId = _accum);
+                    if (liveDocs == null || liveDocs.Get(accum))
+                    {
+                        return (docID = accum);
+                    }
+
                 }
             }
 
             public override int Advance(int target)
             {
-                return _docId = SlowAdvance(target);
+                return docID = SlowAdvance(target);
             }
 
             public override long Cost()
             {
-                return _cost;
+                return cost;
             }
 
             public override int Freq()
             {
-                return _freq;
+                return freq;
             }
         }
 
         internal class PulsingDocsAndPositionsEnum : DocsAndPositionsEnum
         {
-            private byte[] _postingsBytes;
-            private readonly ByteArrayDataInput _postings = new ByteArrayDataInput();
-            private readonly bool _storePayloads;
-            private readonly bool _storeOffsets;
+            private byte[] postingsBytes;
+            private readonly ByteArrayDataInput postings = new ByteArrayDataInput();
+            private readonly bool storePayloads;
+            private readonly bool storeOffsets;
             // note: we could actually reuse across different options, if we passed this to reset()
             // and re-init'ed storeOffsets accordingly (made it non-final)
-            private readonly FieldInfo.IndexOptions? _indexOptions;
-
-            private Bits _liveDocs;
-            private int _docId = -1;
-            private int _accum;
-            private int _freq;
-            private int _posPending;
-            private int _position;
-            private int _payloadLength;
-            private BytesRef _payload;
-            private int _startOffset;
-            private int _offsetLength;
-
-            private bool _payloadRetrieved;
-            private int _cost;
+            private readonly FieldInfo.IndexOptions_e? indexOptions;
+
+            private Bits liveDocs;
+            private int docID = -1;
+            private int accum;
+            private int freq;
+            private int posPending;
+            private int position;
+            private int payloadLength;
+            private BytesRef payload;
+            private int startOffset;
+            private int offsetLength;
+
+            private bool payloadRetrieved;
+            private int cost;
 
             public PulsingDocsAndPositionsEnum(FieldInfo fieldInfo)
             {
-                _indexOptions = fieldInfo.FieldIndexOptions;
-                _storePayloads = fieldInfo.HasPayloads();
-                _storeOffsets =
-                    _indexOptions.Value.CompareTo(FieldInfo.IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS) >= 0;
+                indexOptions = fieldInfo.IndexOptions;
+                storePayloads = fieldInfo.HasPayloads();
+                storeOffsets =
+                    indexOptions.Value.CompareTo(FieldInfo.IndexOptions_e.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS) >= 0;
             }
 
-            public PulsingDocsAndPositionsEnum Reset(Bits liveDocs, PulsingTermState termState)
+            public PulsingDocsAndPositionsEnum reset(Bits liveDocs, PulsingTermState termState)
             {
-                Debug.Assert(termState.PostingsSize != -1);
+                Debug.Debug.Assert((termState.PostingsSize != -1);
 
-                if (_postingsBytes == null)
+                if (postingsBytes == null)
                 {
-                    _postingsBytes = new byte[termState.PostingsSize];
+                    postingsBytes = new byte[termState.PostingsSize];
                 }
-                else if (_postingsBytes.Length < termState.PostingsSize)
+                else if (postingsBytes.Length < termState.PostingsSize)
                 {
-                    _postingsBytes = ArrayUtil.Grow(_postingsBytes, termState.PostingsSize);
+                    postingsBytes = ArrayUtil.Grow(postingsBytes, termState.PostingsSize);
                 }
 
-                Array.Copy(termState.Postings, 0, _postingsBytes, 0, termState.PostingsSize);
-                _postings.Reset(_postingsBytes, 0, termState.PostingsSize);
-                this._liveDocs = liveDocs;
-                _payloadLength = 0;
-                _posPending = 0;
-                _docId = -1;
-                _accum = 0;
-                _cost = termState.DocFreq;
-                _startOffset = _storeOffsets ? 0 : -1; // always return -1 if no offsets are stored
-                _offsetLength = 0;
+                System.Array.Copy(termState.Postings, 0, postingsBytes, 0, termState.PostingsSize);
+                postings.Reset(postingsBytes, 0, termState.PostingsSize);
+                this.liveDocs = liveDocs;
+                payloadLength = 0;
+                posPending = 0;
+                docID = -1;
+                accum = 0;
+                cost = termState.DocFreq;
+                startOffset = storeOffsets ? 0 : -1; // always return -1 if no offsets are stored
+                offsetLength = 0;
                 //System.out.println("PR d&p reset storesPayloads=" + storePayloads + " bytes=" + bytes.length + " this=" + this);
                 return this;
             }
 
             public bool CanReuse(FieldInfo fieldInfo)
             {
-                return _indexOptions == fieldInfo.FieldIndexOptions && _storePayloads == fieldInfo.HasPayloads();
+                return indexOptions == fieldInfo.IndexOptions && storePayloads == fieldInfo.HasPayloads();
             }
 
             public override int NextDoc()
             {
+
                 while (true)
                 {
+
                     SkipPositions();
 
-                    if (_postings.Eof())
+                    if (postings.Eof())
                     {
-                        return _docId = NO_MORE_DOCS;
+                        return docID = NO_MORE_DOCS;
                     }
 
-                    var code = _postings.ReadVInt();
-                    _accum += (int)((uint)code >> 1); // shift off low bit 
-                    _freq = (code & 1) != 0 ? 1 : _postings.ReadVInt();
-                    _posPending = _freq;
-                    _startOffset = _storeOffsets ? 0 : -1; // always return -1 if no offsets are stored
-
-                    if (_liveDocs != null && !_liveDocs.Get(_accum)) continue;
+                    int code = postings.ReadVInt();
+                    accum += (int)((uint)code >> 1); // shift off low bit 
+                    if ((code & 1) != 0)
+                    {
+                        // if low bit is set
+                        freq = 1; // freq is one
+                    }
+                    else
+                    {
+                        freq = postings.ReadVInt(); // else read freq
+                    }
+                    posPending = freq;
+                    startOffset = storeOffsets ? 0 : -1; // always return -1 if no offsets are stored
 
-                    _position = 0;
-                    return (_docId = _accum);
+                    if (liveDocs == null || liveDocs.Get(accum))
+                    {
+                        position = 0;
+                        return (docID = accum);
+                    }
                 }
             }
 
             public override int Freq()
             {
-                return _freq;
+                return freq;
             }
 
             public override int DocID()
             {
-                return _docId;
+                return docID;
             }
 
             public override int Advance(int target)
             {
-                return _docId = SlowAdvance(target);
+                return docID = SlowAdvance(target);
             }
 
             public override int NextPosition()
             {
-                Debug.Assert(_posPending > 0);
+                Debug.Debug.Assert((posPending > 0);
 
-                _posPending--;
+                posPending--;
 
-                if (_storePayloads)
+                if (storePayloads)
                 {
-                    if (!_payloadRetrieved)
+                    if (!payloadRetrieved)
                     {
-                        _postings.SkipBytes(_payloadLength);
+                        postings.SkipBytes(payloadLength);
                     }
-                    int code = _postings.ReadVInt();
+                    int code = postings.ReadVInt();
                     if ((code & 1) != 0)
                     {
-                        _payloadLength = _postings.ReadVInt();
+                        payloadLength = postings.ReadVInt();
                     }
-                    _position += (int)((uint)code >> 1);
-                    _payloadRetrieved = false;
+                    position += (int)((uint)code >> 1);
+                    payloadRetrieved = false;
                 }
                 else
                 {
-                    _position += _postings.ReadVInt();
+                    position += postings.ReadVInt();
                 }
 
-                if (_storeOffsets)
+                if (storeOffsets)
                 {
-                    int offsetCode = _postings.ReadVInt();
+                    int offsetCode = postings.ReadVInt();
                     if ((offsetCode & 1) != 0)
                     {
                         // new offset length
-                        _offsetLength = _postings.ReadVInt();
+                        offsetLength = postings.ReadVInt();
                     }
-                    _startOffset += (int)((uint)offsetCode >> 1);
+                    startOffset += (int)((uint)offsetCode >> 1);
                 }
 
-                return _position;
+                return position;
             }
 
             public override int StartOffset()
             {
-                return _startOffset;
+                return startOffset;
             }
 
             public override int EndOffset()
             {
-                return _startOffset + _offsetLength;
+                return startOffset + offsetLength;
             }
 
             public override BytesRef Payload
             {
                 get
                 {
-                    if (_payloadRetrieved)
-                        return _payload;
-                    
-                    if (_storePayloads && _payloadLength > 0)
+                    if (payloadRetrieved)
+                    {
+                        return payload;
+                    }
+                    else if (storePayloads && payloadLength > 0)
                     {
-                        _payloadRetrieved = true;
-                        if (_payload == null)
+                        payloadRetrieved = true;
+                        if (payload == null)
                         {
-                            _payload = new BytesRef(_payloadLength);
+                            payload = new BytesRef(payloadLength);
                         }
                         else
                         {
-                            _payload.Grow(_payloadLength);
+                            payload.Grow(payloadLength);
                         }
-                        _postings.ReadBytes(_payload.Bytes, 0, _payloadLength);
-                        _payload.Length = _payloadLength;
-                        return _payload;
+                        postings.ReadBytes(payload.Bytes, 0, payloadLength);
+                        payload.Length = payloadLength;
+                        return payload;
+                    }
+                    else
+                    {
+                        return null;
                     }
-                    
-                    return null;
                 }
             }
 
             private void SkipPositions()
             {
-                while (_posPending != 0)
+                while (posPending != 0)
                 {
                     NextPosition();
                 }
-                if (_storePayloads && !_payloadRetrieved)
+                if (storePayloads && !payloadRetrieved)
                 {
-                    _postings.SkipBytes(_payloadLength);
-                    _payloadRetrieved = true;
+                    postings.SkipBytes(payloadLength);
+                    payloadRetrieved = true;
                 }
             }
             
             public override long Cost()
             {
-                return _cost;
+                return cost;
             }
         }
         

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/ce4970c8/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsWriter.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsWriter.cs b/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsWriter.cs
index 82fe52a..528f8de 100644
--- a/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsWriter.cs
+++ b/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsWriter.cs
@@ -15,14 +15,15 @@
  * limitations under the License.
  */
 
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using Lucene.Net.Index;
+using Lucene.Net.Store;
+using Lucene.Net.Util;
+
 namespace Lucene.Net.Codecs.Pulsing
 {
-    using System;
-    using System.Collections.Generic;
-    using System.Diagnostics;
-    using Index;
-    using Store;
-    using Util;
 
     /// <summary>
     /// TODO: we now inline based on total TF of the term,
@@ -52,36 +53,36 @@ namespace Lucene.Net.Codecs.Pulsing
         internal static readonly int VERSION_META_ARRAY = 1;
         internal static readonly int VERSION_CURRENT = VERSION_META_ARRAY;
 
-        private readonly SegmentWriteState _segmentState;
-        private IndexOutput _termsOut;
-        private readonly List<FieldMetaData> _fields;
-        private FieldInfo.IndexOptions? _indexOptions;
-        private bool _storePayloads;
+        private SegmentWriteState segmentState;
+        private IndexOutput termsOut;
+        private List<FieldMetaData> fields;
+        private FieldInfo.IndexOptions_e? indexOptions;
+        private bool storePayloads;
 
         // information for wrapped PF, in current field
-        private int _longsSize;
-        private long[] _longs;
-        private bool _absolute;
+        private int longsSize;
+        private long[] longs;
+        private bool absolute;
 
         private class PulsingTermState : BlockTermState
         {
-            internal byte[] BYTES;
-            internal BlockTermState WRAPPED_STATE;
+            internal byte[] bytes;
+            internal BlockTermState wrappedState;
 
             public override String ToString()
             {
-                if (BYTES != null)
+                if (bytes != null)
                 {
                     return "inlined";
                 }
-                return "not inlined wrapped=" + WRAPPED_STATE;
+                return "not inlined wrapped=" + wrappedState;
             }
         }
 
         // one entry per position
-        private readonly Position[] _pending;
-        private int _pendingCount = 0;   // -1 once we've hit too many positions
-        private Position _currentDoc;    // first Position entry of current doc
+        private Position[] pending;
+        private int pendingCount = 0;   // -1 once we've hit too many positions
+        private Position currentDoc;    // first Position entry of current doc
 
         private sealed class Position
         {
@@ -113,42 +114,43 @@ namespace Lucene.Net.Codecs.Pulsing
 
         /// <summary>
         /// If the total number of positions (summed across all docs
-        /// for this term) is less than or equal maxPositions, then the postings are
+        /// for this term) is <= maxPositions, then the postings are
         /// inlined into terms dict
         /// </summary>
         public PulsingPostingsWriter(SegmentWriteState state, int maxPositions, PostingsWriterBase wrappedPostingsWriter)
         {
 
-            _pending = new Position[maxPositions];
-            for (var i = 0; i < maxPositions; i++)
+            pending = new Position[maxPositions];
+            for (int i = 0; i < maxPositions; i++)
             {
-                _pending[i] = new Position();
+                pending[i] = new Position();
             }
-            _fields = new List<FieldMetaData>();
+            fields = new List<FieldMetaData>();
 
             // We simply wrap another postings writer, but only call
             // on it when tot positions is >= the cutoff:
-            _wrappedPostingsWriter = wrappedPostingsWriter;
-            _segmentState = state;
+            this._wrappedPostingsWriter = wrappedPostingsWriter;
+            this.segmentState = state;
         }
 
         public override void Init(IndexOutput termsOut)
         {
-            _termsOut = termsOut;
+            this.termsOut = termsOut;
             CodecUtil.WriteHeader(termsOut, CODEC, VERSION_CURRENT);
-            termsOut.WriteVInt(_pending.Length); // encode maxPositions in header
+            termsOut.WriteVInt(pending.Length); // encode maxPositions in header
             _wrappedPostingsWriter.Init(termsOut);
         }
 
         public override BlockTermState NewTermState()
         {
-            var state = new PulsingTermState {WRAPPED_STATE = _wrappedPostingsWriter.NewTermState()};
+            PulsingTermState state = new PulsingTermState();
+            state.wrappedState = _wrappedPostingsWriter.NewTermState();
             return state;
         }
 
         public override void StartTerm()
         {
-            Debug.Assert(_pendingCount == 0);
+            Debug.Debug.Assert((pendingCount == 0);
         }
 
         /// <summary>
@@ -161,61 +163,61 @@ namespace Lucene.Net.Codecs.Pulsing
         /// <returns></returns>
         public override int SetField(FieldInfo fieldInfo)
         {
-            _indexOptions = fieldInfo.FieldIndexOptions;
-            _storePayloads = fieldInfo.HasPayloads();
-            _absolute = false;
-            _longsSize = _wrappedPostingsWriter.SetField(fieldInfo);
-            _longs = new long[_longsSize];
-            _fields.Add(new FieldMetaData(fieldInfo.Number, _longsSize));
+            this.indexOptions = fieldInfo.IndexOptions;
+            storePayloads = fieldInfo.HasPayloads();
+            absolute = false;
+            longsSize = _wrappedPostingsWriter.SetField(fieldInfo);
+            longs = new long[longsSize];
+            fields.Add(new FieldMetaData(fieldInfo.Number, longsSize));
             return 0;
         }
 
-        public override void StartDoc(int docId, int termDocFreq)
+        public override void StartDoc(int docID, int termDocFreq)
         {
-            Debug.Assert(docId >= 0, "Got DocID=" + docId);
+            Debug.Debug.Assert((docID >= 0, "Got DocID=" + docID);
 
-            if (_pendingCount == _pending.Length)
+            if (pendingCount == pending.Length)
             {
-                Push();
+                push();
                 _wrappedPostingsWriter.FinishDoc();
             }
 
-            if (_pendingCount != -1)
+            if (pendingCount != -1)
             {
-                Debug.Assert(_pendingCount < _pending.Length);
-                _currentDoc = _pending[_pendingCount];
-                _currentDoc.docID = docId;
-                if (_indexOptions == FieldInfo.IndexOptions.DOCS_ONLY)
+                Debug.Debug.Assert((pendingCount < pending.Length);
+                currentDoc = pending[pendingCount];
+                currentDoc.docID = docID;
+                if (indexOptions == FieldInfo.IndexOptions_e.DOCS_ONLY)
                 {
-                    _pendingCount++;
+                    pendingCount++;
                 }
-                else if (_indexOptions == FieldInfo.IndexOptions.DOCS_AND_FREQS)
+                else if (indexOptions == FieldInfo.IndexOptions_e.DOCS_AND_FREQS)
                 {
-                    _pendingCount++;
-                    _currentDoc.termFreq = termDocFreq;
+                    pendingCount++;
+                    currentDoc.termFreq = termDocFreq;
                 }
                 else
                 {
-                    _currentDoc.termFreq = termDocFreq;
+                    currentDoc.termFreq = termDocFreq;
                 }
             }
             else
             {
                 // We've already seen too many docs for this term --
                 // just forward to our fallback writer
-                _wrappedPostingsWriter.StartDoc(docId, termDocFreq);
+                _wrappedPostingsWriter.StartDoc(docID, termDocFreq);
             }
         }
 
         public override void AddPosition(int position, BytesRef payload, int startOffset, int endOffset)
         {
 
-            if (_pendingCount == _pending.Length)
+            if (pendingCount == pending.Length)
             {
-                Push();
+                push();
             }
 
-            if (_pendingCount == -1)
+            if (pendingCount == -1)
             {
                 // We've already seen too many docs for this term --
                 // just forward to our fallback writer
@@ -224,11 +226,11 @@ namespace Lucene.Net.Codecs.Pulsing
             else
             {
                 // buffer up
-                Position pos = _pending[_pendingCount++];
+                Position pos = pending[pendingCount++];
                 pos.pos = position;
                 pos.startOffset = startOffset;
                 pos.endOffset = endOffset;
-                pos.docID = _currentDoc.docID;
+                pos.docID = currentDoc.docID;
                 if (payload != null && payload.Length > 0)
                 {
                     if (pos.payload == null)
@@ -249,13 +251,13 @@ namespace Lucene.Net.Codecs.Pulsing
 
         public override void FinishDoc()
         {
-            if (_pendingCount == -1)
+            if (pendingCount == -1)
             {
                 _wrappedPostingsWriter.FinishDoc();
             }
         }
 
-        private readonly RAMOutputStream _buffer = new RAMOutputStream();
+        private readonly RAMOutputStream buffer = new RAMOutputStream();
 
         /// <summary>
         /// Called when we are done adding docs to this term
@@ -263,16 +265,16 @@ namespace Lucene.Net.Codecs.Pulsing
         /// <param name="_state"></param>
         public override void FinishTerm(BlockTermState _state)
         {
-            var state = (PulsingTermState) _state;
+            PulsingTermState state = (PulsingTermState) _state;
 
-            Debug.Assert(_pendingCount > 0 || _pendingCount == -1);
+            Debug.Debug.Assert((pendingCount > 0 || pendingCount == -1);
 
-            if (_pendingCount == -1)
+            if (pendingCount == -1)
             {
-                state.WRAPPED_STATE.DocFreq = state.DocFreq;
-                state.WRAPPED_STATE.TotalTermFreq = state.TotalTermFreq;
-                state.BYTES = null;
-                _wrappedPostingsWriter.FinishTerm(state.WRAPPED_STATE);
+                state.wrappedState.DocFreq = state.DocFreq;
+                state.wrappedState.TotalTermFreq = state.TotalTermFreq;
+                state.bytes = null;
+                _wrappedPostingsWriter.FinishTerm(state.wrappedState);
             }
             else
             {
@@ -286,72 +288,72 @@ namespace Lucene.Net.Codecs.Pulsing
                 // given codec wants to store other interesting
                 // stuff, it could use this pulsing codec to do so
 
-                if (_indexOptions >= FieldInfo.IndexOptions.DOCS_AND_FREQS_AND_POSITIONS)
+                if (indexOptions.Value.CompareTo(FieldInfo.IndexOptions_e.DOCS_AND_FREQS_AND_POSITIONS) >= 0)
                 {
-                    var lastDocID = 0;
-                    var pendingIDX = 0;
-                    var lastPayloadLength = -1;
-                    var lastOffsetLength = -1;
-                    while (pendingIDX < _pendingCount)
+                    int lastDocID = 0;
+                    int pendingIDX = 0;
+                    int lastPayloadLength = -1;
+                    int lastOffsetLength = -1;
+                    while (pendingIDX < pendingCount)
                     {
-                        var doc = _pending[pendingIDX];
+                        Position doc = pending[pendingIDX];
 
-                        var delta = doc.docID - lastDocID;
+                        int delta = doc.docID - lastDocID;
                         lastDocID = doc.docID;
 
                         // if (DEBUG) System.out.println("  write doc=" + doc.docID + " freq=" + doc.termFreq);
 
                         if (doc.termFreq == 1)
                         {
-                            _buffer.WriteVInt((delta << 1) | 1);
+                            buffer.WriteVInt((delta << 1) | 1);
                         }
                         else
                         {
-                            _buffer.WriteVInt(delta << 1);
-                            _buffer.WriteVInt(doc.termFreq);
+                            buffer.WriteVInt(delta << 1);
+                            buffer.WriteVInt(doc.termFreq);
                         }
 
-                        var lastPos = 0;
-                        var lastOffset = 0;
-                        for (var posIDX = 0; posIDX < doc.termFreq; posIDX++)
+                        int lastPos = 0;
+                        int lastOffset = 0;
+                        for (int posIDX = 0; posIDX < doc.termFreq; posIDX++)
                         {
-                            var pos = _pending[pendingIDX++];
-                            Debug.Assert(pos.docID == doc.docID);
-                            var posDelta = pos.pos - lastPos;
+                            Position pos = pending[pendingIDX++];
+                            Debug.Debug.Assert((pos.docID == doc.docID);
+                            int posDelta = pos.pos - lastPos;
                             lastPos = pos.pos;
                             
-                            var payloadLength = pos.payload == null ? 0 : pos.payload.Length;
-                            if (_storePayloads)
+                            int payloadLength = pos.payload == null ? 0 : pos.payload.Length;
+                            if (storePayloads)
                             {
                                 if (payloadLength != lastPayloadLength)
                                 {
-                                    _buffer.WriteVInt((posDelta << 1) | 1);
-                                    _buffer.WriteVInt(payloadLength);
+                                    buffer.WriteVInt((posDelta << 1) | 1);
+                                    buffer.WriteVInt(payloadLength);
                                     lastPayloadLength = payloadLength;
                                 }
                                 else
                                 {
-                                    _buffer.WriteVInt(posDelta << 1);
+                                    buffer.WriteVInt(posDelta << 1);
                                 }
                             }
                             else
                             {
-                                _buffer.WriteVInt(posDelta);
+                                buffer.WriteVInt(posDelta);
                             }
 
-                            if (_indexOptions >= FieldInfo.IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS)
+                            if (indexOptions.Value.CompareTo(FieldInfo.IndexOptions_e.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS) >= 0)
                             {
                                 //System.out.println("write=" + pos.startOffset + "," + pos.endOffset);
-                                var offsetDelta = pos.startOffset - lastOffset;
-                                var offsetLength = pos.endOffset - pos.startOffset;
+                                int offsetDelta = pos.startOffset - lastOffset;
+                                int offsetLength = pos.endOffset - pos.startOffset;
                                 if (offsetLength != lastOffsetLength)
                                 {
-                                    _buffer.WriteVInt(offsetDelta << 1 | 1);
-                                    _buffer.WriteVInt(offsetLength);
+                                    buffer.WriteVInt(offsetDelta << 1 | 1);
+                                    buffer.WriteVInt(offsetLength);
                                 }
                                 else
                                 {
-                                    _buffer.WriteVInt(offsetDelta << 1);
+                                    buffer.WriteVInt(offsetDelta << 1);
                                 }
                                 lastOffset = pos.startOffset;
                                 lastOffsetLength = offsetLength;
@@ -359,79 +361,73 @@ namespace Lucene.Net.Codecs.Pulsing
 
                             if (payloadLength > 0)
                             {
-                                Debug.Assert(_storePayloads);
-                                _buffer.WriteBytes(pos.payload.Bytes, 0, pos.payload.Length);
+                                Debug.Debug.Assert((storePayloads);
+                                buffer.WriteBytes(pos.payload.Bytes, 0, pos.payload.Length);
                             }
                         }
                     }
                 }
-                else switch (_indexOptions)
+                else if (indexOptions == FieldInfo.IndexOptions_e.DOCS_AND_FREQS)
                 {
-                    case FieldInfo.IndexOptions.DOCS_AND_FREQS:
+                    int lastDocID = 0;
+                    for (int posIDX = 0; posIDX < pendingCount; posIDX++)
                     {
-                        var lastDocId = 0;
-                        for (var posIdx = 0; posIdx < _pendingCount; posIdx++)
-                        {
-                            var doc = _pending[posIdx];
-                            var delta = doc.docID - lastDocId;
-
-                            Debug.Assert(doc.termFreq != 0);
+                        Position doc = pending[posIDX];
+                        int delta = doc.docID - lastDocID;
+                        Debug.Debug.Assert((doc.termFreq != 0);
 
-                            if (doc.termFreq == 1)
-                            {
-                                _buffer.WriteVInt((delta << 1) | 1);
-                            }
-                            else
-                            {
-                                _buffer.WriteVInt(delta << 1);
-                                _buffer.WriteVInt(doc.termFreq);
-                            }
-                            lastDocId = doc.docID;
+                        if (doc.termFreq == 1)
+                        {
+                            buffer.WriteVInt((delta << 1) | 1);
                         }
-                    }
-                        break;
-                    case FieldInfo.IndexOptions.DOCS_ONLY:
-                    {
-                        var lastDocId = 0;
-                        for (var posIdx = 0; posIdx < _pendingCount; posIdx++)
+                        else
                         {
-                            var doc = _pending[posIdx];
-                            _buffer.WriteVInt(doc.docID - lastDocId);
-                            lastDocId = doc.docID;
+                            buffer.WriteVInt(delta << 1);
+                            buffer.WriteVInt(doc.termFreq);
                         }
+                        lastDocID = doc.docID;
+                    }
+                }
+                else if (indexOptions == FieldInfo.IndexOptions_e.DOCS_ONLY)
+                {
+                    int lastDocID = 0;
+                    for (int posIDX = 0; posIDX < pendingCount; posIDX++)
+                    {
+                        Position doc = pending[posIDX];
+                        buffer.WriteVInt(doc.docID - lastDocID);
+                        lastDocID = doc.docID;
                     }
-                        break;
                 }
 
-                state.BYTES = new byte[(int) _buffer.FilePointer];
-                _buffer.WriteTo((sbyte[])(Array)state.BYTES, 0);
-                _buffer.Reset();
+                state.bytes = new byte[(int) buffer.FilePointer];
+                buffer.WriteTo((sbyte[])(Array)state.bytes, 0);
+                buffer.Reset();
             }
-            _pendingCount = 0;
+            pendingCount = 0;
         }
 
-        public override void EncodeTerm(long[] empty, DataOutput output, FieldInfo fieldInfo, BlockTermState state,
-            bool abs)
+        public override void EncodeTerm(long[] empty, DataOutput output, FieldInfo fieldInfo, BlockTermState _state,
+            bool absolute)
         {
-            var _state = (PulsingTermState) state;
-            Debug.Assert(empty.Length == 0);
-            _absolute = _absolute || abs;
-            if (_state.BYTES == null)
+            PulsingTermState state = (PulsingTermState) _state;
+            Debug.Debug.Assert((empty.Length == 0);
+            this.absolute = this.absolute || absolute;
+            if (state.bytes == null)
             {
-                _wrappedPostingsWriter.EncodeTerm(_longs, _buffer, fieldInfo, _state.WRAPPED_STATE, _absolute);
-                for (var i = 0; i < _longsSize; i++)
+                _wrappedPostingsWriter.EncodeTerm(longs, buffer, fieldInfo, state.wrappedState, this.absolute);
+                for (int i = 0; i < longsSize; i++)
                 {
-                    output.WriteVLong(_longs[i]);
+                    output.WriteVLong(longs[i]);
                 }
-                _buffer.WriteTo(output);
-                _buffer.Reset();
-                _absolute = false;
+                buffer.WriteTo(output);
+                buffer.Reset();
+                this.absolute = false;
             }
             else
             {
-                output.WriteVInt(_state.BYTES.Length);
-                output.WriteBytes(_state.BYTES, 0, _state.BYTES.Length);
-                _absolute = _absolute || abs;
+                output.WriteVInt(state.bytes.Length);
+                output.WriteBytes(state.bytes, 0, state.bytes.Length);
+                this.absolute = this.absolute || absolute;
             }
         }
 
@@ -445,16 +441,16 @@ namespace Lucene.Net.Codecs.Pulsing
                 return;
             }
 
-            var summaryFileName = IndexFileNames.SegmentFileName(_segmentState.SegmentInfo.Name,
-                _segmentState.SegmentSuffix, SUMMARY_EXTENSION);
+            String summaryFileName = IndexFileNames.SegmentFileName(segmentState.SegmentInfo.Name,
+                segmentState.SegmentSuffix, SUMMARY_EXTENSION);
             IndexOutput output = null;
             try
             {
                 output =
-                    _segmentState.Directory.CreateOutput(summaryFileName, _segmentState.Context);
+                    segmentState.Directory.CreateOutput(summaryFileName, segmentState.Context);
                 CodecUtil.WriteHeader(output, CODEC, VERSION_CURRENT);
-                output.WriteVInt(_fields.Count);
-                foreach (var field in _fields)
+                output.WriteVInt(fields.Count);
+                foreach (FieldMetaData field in fields)
                 {
                     output.WriteVInt(field.FieldNumber);
                     output.WriteVInt(field.LongsSize);
@@ -468,43 +464,48 @@ namespace Lucene.Net.Codecs.Pulsing
         }
 
         // Pushes pending positions to the wrapped codec
-        private void Push()
+        private void push()
         {
-            Debug.Assert(_pendingCount == _pending.Length);
+            // if (DEBUG) System.out.println("PW now push @ " + pendingCount + " wrapped=" + wrappedPostingsWriter);
+            Debug.Debug.Assert((pendingCount == pending.Length);
 
             _wrappedPostingsWriter.StartTerm();
 
             // Flush all buffered docs
-            if (_indexOptions >= FieldInfo.IndexOptions.DOCS_AND_FREQS_AND_POSITIONS)
+            if (indexOptions.Value.CompareTo(FieldInfo.IndexOptions_e.DOCS_AND_FREQS_AND_POSITIONS) >= 0)
             {
                 Position doc = null;
 
-                foreach(var pos in _pending)
+                foreach(Position pos in pending)
                 {
                     if (doc == null)
                     {
                         doc = pos;
+                        // if (DEBUG) System.out.println("PW: wrapped.startDoc docID=" + doc.docID + " tf=" + doc.termFreq);
                         _wrappedPostingsWriter.StartDoc(doc.docID, doc.termFreq);
                     }
                     else if (doc.docID != pos.docID)
                     {
-                        Debug.Assert(pos.docID > doc.docID);
+                        Debug.Debug.Assert((pos.docID > doc.docID);
+                        // if (DEBUG) System.out.println("PW: wrapped.finishDoc");
                         _wrappedPostingsWriter.FinishDoc();
                         doc = pos;
+                        // if (DEBUG) System.out.println("PW: wrapped.startDoc docID=" + doc.docID + " tf=" + doc.termFreq);
                         _wrappedPostingsWriter.StartDoc(doc.docID, doc.termFreq);
                     }
+                    // if (DEBUG) System.out.println("PW:   wrapped.addPos pos=" + pos.pos);
                     _wrappedPostingsWriter.AddPosition(pos.pos, pos.payload, pos.startOffset, pos.endOffset);
                 }
                 //wrappedPostingsWriter.finishDoc();
             }
             else
             {
-                foreach(var doc in _pending)
+                foreach(Position doc in pending)
                 {
-                    _wrappedPostingsWriter.StartDoc(doc.docID, _indexOptions == FieldInfo.IndexOptions.DOCS_ONLY ? 0 : doc.termFreq);
+                    _wrappedPostingsWriter.StartDoc(doc.docID, indexOptions == FieldInfo.IndexOptions_e.DOCS_ONLY ? 0 : doc.termFreq);
                 }
             }
-            _pendingCount = -1;
+            pendingCount = -1;
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/ce4970c8/src/Lucene.Net.Core/Codecs/PostingsConsumer.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Codecs/PostingsConsumer.cs b/src/Lucene.Net.Core/Codecs/PostingsConsumer.cs
index 65710df..8515972 100644
--- a/src/Lucene.Net.Core/Codecs/PostingsConsumer.cs
+++ b/src/Lucene.Net.Core/Codecs/PostingsConsumer.cs
@@ -63,7 +63,7 @@ namespace Lucene.Net.Codecs
         /// <code>freq</code> will be -1 when term frequencies are omitted
         /// for the field.
         /// </summary>
-        public abstract void StartDoc(int docId, int freq);
+        public abstract void StartDoc(int docID, int freq);
 
         /// <summary>
         /// Add a new position & payload, and start/end offset.  A


[17/26] lucenenet git commit: first commit of facet porting, failing tests will be fixed in next commits.

Posted by sy...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/FacetsCollector.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/FacetsCollector.cs b/Lucene.Net.Facet/FacetsCollector.cs
new file mode 100644
index 0000000..4bbb76f
--- /dev/null
+++ b/Lucene.Net.Facet/FacetsCollector.cs
@@ -0,0 +1,372 @@
+using System;
+using System.Collections.Generic;
+using Lucene.Net.Index;
+using Lucene.Net.Search;
+using Lucene.Net.Util;
+
+namespace Lucene.Net.Facet
+{
+
+    /*
+     * 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.
+     */
+    /// <summary>
+    /// Collects hits for subsequent faceting.  Once you've run
+    ///  a search and collect hits into this, instantiate one of
+    ///  the <seealso cref="Collector"/> subclasses to do the facet
+    ///  counting.  Use the {@code search} utility methods to
+    ///  perform an "ordinary" search but also collect into a
+    ///  <seealso cref="Facets"/>. 
+    /// </summary>
+    public class FacetsCollector : Collector
+    {
+
+        private AtomicReaderContext context;
+        private Scorer scorer;
+        private int totalHits;
+        private float[] scores;
+        private readonly bool keepScores;
+        private readonly IList<MatchingDocs> matchingDocs = new List<MatchingDocs>();
+        private Docs docs;
+
+        /// <summary>
+        /// Used during collection to record matching docs and then return a
+        /// <seealso cref="DocIdSet"/> that contains them.
+        /// </summary>
+        protected internal abstract class Docs
+        {
+
+            /// <summary>
+            /// Solr constructor. </summary>
+            public Docs()
+            {
+            }
+
+            /// <summary>
+            /// Record the given document. </summary>
+            public abstract void AddDoc(int docId);
+
+            /// <summary>
+            /// Return the <seealso cref="DocIdSet"/> which contains all the recorded docs. </summary>
+            public abstract DocIdSet DocIdSet { get; }
+        }
+
+        /// <summary>
+        /// Holds the documents that were matched in the <seealso cref="AtomicReaderContext"/>.
+        /// If scores were required, then {@code scores} is not null.
+        /// </summary>
+        public sealed class MatchingDocs
+        {
+
+            /// <summary>
+            /// Context for this segment. </summary>
+            public readonly AtomicReaderContext context;
+
+            /// <summary>
+            /// Which documents were seen. </summary>
+            public readonly DocIdSet bits;
+
+            /// <summary>
+            /// Non-sparse scores array. </summary>
+            public readonly float[] scores;
+
+            /// <summary>
+            /// Total number of hits </summary>
+            public readonly int totalHits;
+
+            /// <summary>
+            /// Sole constructor. </summary>
+            public MatchingDocs(AtomicReaderContext context, DocIdSet bits, int totalHits, float[] scores)
+            {
+                this.context = context;
+                this.bits = bits;
+                this.scores = scores;
+                this.totalHits = totalHits;
+            }
+        }
+
+        /// <summary>
+        /// Default constructor </summary>
+        public FacetsCollector()
+            : this(false)
+        {
+        }
+
+        /// <summary>
+        /// Create this; if {@code keepScores} is true then a
+        ///  float[] is allocated to hold score of all hits. 
+        /// </summary>
+        public FacetsCollector(bool keepScores)
+        {
+            this.keepScores = keepScores;
+        }
+
+        /// <summary>
+        /// Creates a <seealso cref="Docs"/> to record hits. The default uses <seealso cref="FixedBitSet"/>
+        /// to record hits and you can override to e.g. record the docs in your own
+        /// <seealso cref="DocIdSet"/>.
+        /// </summary>
+        protected virtual Docs CreateDocs(int maxDoc)
+        {
+            return new DocsAnonymousInnerClassHelper(this, maxDoc);
+        }
+
+        private class DocsAnonymousInnerClassHelper : Docs
+        {
+            private readonly FacetsCollector outerInstance;
+
+            private int maxDoc;
+
+            public DocsAnonymousInnerClassHelper(FacetsCollector outerInstance, int maxDoc)
+            {
+                this.outerInstance = outerInstance;
+                this.maxDoc = maxDoc;
+                bits = new FixedBitSet(maxDoc);
+            }
+
+            private readonly FixedBitSet bits;
+
+            public override void AddDoc(int docId)
+            {
+                bits.Set(docId);
+            }
+
+            public override DocIdSet DocIdSet
+            {
+                get
+                {
+                    return bits;
+                }
+            }
+        }
+
+        /// <summary>
+        /// True if scores were saved. </summary>
+        public bool KeepScores
+        {
+            get
+            {
+                return keepScores;
+            }
+        }
+
+        /// <summary>
+        /// Returns the documents matched by the query, one <seealso cref="GetMatchingDocs"/> per
+        /// visited segment.
+        /// </summary>
+        public virtual IList<MatchingDocs> GetMatchingDocs
+        {
+            get
+            {
+                if (docs != null)
+                {
+                    matchingDocs.Add(new MatchingDocs(this.context, docs.DocIdSet, totalHits, scores));
+                    docs = null;
+                    scores = null;
+                    context = null;
+                }
+
+                return matchingDocs;
+            }
+        }
+
+        public override sealed bool AcceptsDocsOutOfOrder()
+        {
+            // If we are keeping scores then we require in-order
+            // because we append each score to the float[] and
+            // expect that they correlate in order to the hits:
+            return keepScores == false;
+        }
+
+        public override sealed void Collect(int doc)
+        {
+            docs.AddDoc(doc);
+            if (keepScores)
+            {
+                if (totalHits >= scores.Length)
+                {
+                    float[] newScores = new float[ArrayUtil.Oversize(totalHits + 1, 4)];
+                    Array.Copy(scores, 0, newScores, 0, totalHits);
+                    scores = newScores;
+                }
+                scores[totalHits] = scorer.Score();
+            }
+            totalHits++;
+        }
+
+        public override sealed Scorer Scorer
+        {
+            set
+            {
+                this.scorer = value;
+            }
+        }
+
+        public override sealed AtomicReaderContext NextReader
+        {
+            set
+            {
+                if (docs != null)
+                {
+                    matchingDocs.Add(new MatchingDocs(this.context, docs.DocIdSet, totalHits, scores));
+                }
+                docs = CreateDocs(value.Reader.MaxDoc);
+                totalHits = 0;
+                if (keepScores)
+                {
+                    scores = new float[64]; // some initial size
+                }
+                this.context = value;
+            }
+        }
+
+
+        /// <summary>
+        /// Utility method, to search and also collect all hits
+        ///  into the provided <seealso cref="Collector"/>. 
+        /// </summary>
+        public static TopDocs Search(IndexSearcher searcher, Query q, int n, Collector fc)
+        {
+            return DoSearch(searcher, null, q, null, n, null, false, false, fc);
+        }
+
+        /// <summary>
+        /// Utility method, to search and also collect all hits
+        ///  into the provided <seealso cref="Collector"/>. 
+        /// </summary>
+        public static TopDocs Search(IndexSearcher searcher, Query q, Filter filter, int n, Collector fc)
+        {
+            return DoSearch(searcher, null, q, filter, n, null, false, false, fc);
+        }
+
+        /// <summary>
+        /// Utility method, to search and also collect all hits
+        ///  into the provided <seealso cref="Collector"/>. 
+        /// </summary>
+        public static TopFieldDocs Search(IndexSearcher searcher, Query q, Filter filter, int n, Sort sort, Collector fc)
+        {
+            if (sort == null)
+            {
+                throw new System.ArgumentException("sort must not be null");
+            }
+            return (TopFieldDocs)DoSearch(searcher, null, q, filter, n, sort, false, false, fc);
+        }
+
+        /// <summary>
+        /// Utility method, to search and also collect all hits
+        ///  into the provided <seealso cref="Collector"/>. 
+        /// </summary>
+        public static TopFieldDocs Search(IndexSearcher searcher, Query q, Filter filter, int n, Sort sort, bool doDocScores, bool doMaxScore, Collector fc)
+        {
+            if (sort == null)
+            {
+                throw new System.ArgumentException("sort must not be null");
+            }
+            return (TopFieldDocs)DoSearch(searcher, null, q, filter, n, sort, doDocScores, doMaxScore, fc);
+        }
+
+        /// <summary>
+        /// Utility method, to search and also collect all hits
+        ///  into the provided <seealso cref="Collector"/>. 
+        /// </summary>
+        public virtual TopDocs SearchAfter(IndexSearcher searcher, ScoreDoc after, Query q, int n, Collector fc)
+        {
+            return DoSearch(searcher, after, q, null, n, null, false, false, fc);
+        }
+
+        /// <summary>
+        /// Utility method, to search and also collect all hits
+        ///  into the provided <seealso cref="Collector"/>. 
+        /// </summary>
+        public static TopDocs SearchAfter(IndexSearcher searcher, ScoreDoc after, Query q, Filter filter, int n, Collector fc)
+        {
+            return DoSearch(searcher, after, q, filter, n, null, false, false, fc);
+        }
+
+        /// <summary>
+        /// Utility method, to search and also collect all hits
+        ///  into the provided <seealso cref="Collector"/>. 
+        /// </summary>
+        public static TopDocs SearchAfter(IndexSearcher searcher, ScoreDoc after, Query q, Filter filter, int n, Sort sort, Collector fc)
+        {
+            if (sort == null)
+            {
+                throw new System.ArgumentException("sort must not be null");
+            }
+            return DoSearch(searcher, after, q, filter, n, sort, false, false, fc);
+        }
+
+        /// <summary>
+        /// Utility method, to search and also collect all hits
+        ///  into the provided <seealso cref="Collector"/>. 
+        /// </summary>
+        public static TopDocs SearchAfter(IndexSearcher searcher, ScoreDoc after, Query q, Filter filter, int n, Sort sort, bool doDocScores, bool doMaxScore, Collector fc)
+        {
+            if (sort == null)
+            {
+                throw new System.ArgumentException("sort must not be null");
+            }
+            return DoSearch(searcher, after, q, filter, n, sort, doDocScores, doMaxScore, fc);
+        }
+
+        private static TopDocs DoSearch(IndexSearcher searcher, ScoreDoc after, Query q, Filter filter, int n, Sort sort, bool doDocScores, bool doMaxScore, Collector fc)
+        {
+
+            if (filter != null)
+            {
+                q = new FilteredQuery(q, filter);
+            }
+
+            int limit = searcher.IndexReader.MaxDoc;
+            if (limit == 0)
+            {
+                limit = 1;
+            }
+            n = Math.Min(n, limit);
+
+            if (after != null && after.Doc >= limit)
+            {
+                throw new System.ArgumentException("after.doc exceeds the number of documents in the reader: after.doc=" + after.Doc + " limit=" + limit);
+            }
+
+
+            if (sort != null)
+            {
+                if (after != null && !(after is FieldDoc))
+                {
+                    // TODO: if we fix type safety of TopFieldDocs we can
+                    // remove this
+                    throw new System.ArgumentException("after must be a FieldDoc; got " + after);
+                }
+                const bool fillFields = true;
+                var hitsCollector = TopFieldCollector.Create(sort, n, (FieldDoc)after, fillFields, doDocScores, doMaxScore, false);
+                searcher.Search(q, MultiCollector.Wrap(hitsCollector, fc));
+                return hitsCollector.TopDocs();
+            }
+            else
+            {
+                // TODO: can we pass the right boolean for
+                // in-order instead of hardwired to false...?  we'd
+                // need access to the protected IS.search methods
+                // taking Weight... could use reflection...
+                var hitsCollector = TopScoreDocCollector.Create(n, after, false);
+                searcher.Search(q, MultiCollector.Wrap(hitsCollector, fc));
+                return hitsCollector.TopDocs();
+            }
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/FacetsConfig.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/FacetsConfig.cs b/Lucene.Net.Facet/FacetsConfig.cs
new file mode 100644
index 0000000..b8f8140
--- /dev/null
+++ b/Lucene.Net.Facet/FacetsConfig.cs
@@ -0,0 +1,703 @@
+using System;
+using System.Diagnostics;
+using System.Collections.Generic;
+using System.Collections.Concurrent;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using Lucene.Net.Facet.SortedSet;
+using Lucene.Net.Facet.Taxonomy;
+
+namespace Lucene.Net.Facet
+{
+
+    /*
+     * 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 BinaryDocValuesField = Lucene.Net.Documents.BinaryDocValuesField;
+    using Document = Lucene.Net.Documents.Document;
+    using Field = Lucene.Net.Documents.Field;
+    using SortedSetDocValuesField = Lucene.Net.Documents.SortedSetDocValuesField;
+    using StringField = Lucene.Net.Documents.StringField;
+    using SortedSetDocValuesFacetField = Lucene.Net.Facet.SortedSet.SortedSetDocValuesFacetField;
+    using AssociationFacetField = Lucene.Net.Facet.Taxonomy.AssociationFacetField;
+    using FacetLabel = Lucene.Net.Facet.Taxonomy.FacetLabel;
+    using FloatAssociationFacetField = Lucene.Net.Facet.Taxonomy.FloatAssociationFacetField;
+    using IntAssociationFacetField = Lucene.Net.Facet.Taxonomy.IntAssociationFacetField;
+    using TaxonomyWriter = Lucene.Net.Facet.Taxonomy.TaxonomyWriter;
+    using IndexableField = Lucene.Net.Index.IndexableField;
+    using IndexableFieldType = Lucene.Net.Index.IndexableFieldType;
+    using ArrayUtil = Lucene.Net.Util.ArrayUtil;
+    using BytesRef = Lucene.Net.Util.BytesRef;
+    using IntsRef = Lucene.Net.Util.IntsRef;
+
+    /// <summary>
+    /// Records per-dimension configuration.  By default a
+    ///  dimension is flat, single valued and does
+    ///  not require count for the dimension; use
+    ///  the setters in this class to change these settings for
+    ///  each dim.
+    /// 
+    ///  <para><b>NOTE</b>: this configuration is not saved into the
+    ///  index, but it's vital, and up to the application to
+    ///  ensure, that at search time the provided {@code
+    ///  FacetsConfig} matches what was used during indexing.
+    /// 
+    ///  @lucene.experimental 
+    /// </para>
+    /// </summary>
+    public class FacetsConfig
+    {
+
+        /// <summary>
+        /// Which Lucene field holds the drill-downs and ords (as
+        ///  doc values). 
+        /// </summary>
+        public const string DEFAULT_INDEX_FIELD_NAME = "$facets";
+
+        private readonly IDictionary<string, DimConfig> fieldTypes = new ConcurrentDictionary<string, DimConfig>();
+
+        // Used only for best-effort detection of app mixing
+        // int/float/bytes in a single indexed field:
+        private readonly IDictionary<string, string> assocDimTypes = new ConcurrentDictionary<string, string>();
+
+        /// <summary>
+        /// Holds the configuration for one dimension
+        /// 
+        /// @lucene.experimental 
+        /// </summary>
+        public sealed class DimConfig
+        {
+            /// <summary>
+            /// True if this dimension is hierarchical. </summary>
+            public bool hierarchical;
+
+            /// <summary>
+            /// True if this dimension is multi-valued. </summary>
+            public bool multiValued;
+
+            /// <summary>
+            /// True if the count/aggregate for the entire dimension
+            ///  is required, which is unusual (default is false). 
+            /// </summary>
+            public bool requireDimCount;
+
+            /// <summary>
+            /// Actual field where this dimension's facet labels
+            ///  should be indexed 
+            /// </summary>
+            public string indexFieldName = DEFAULT_INDEX_FIELD_NAME;
+
+            /// <summary>
+            /// Default constructor. </summary>
+            public DimConfig()
+            {
+            }
+        }
+
+        /// <summary>
+        /// Default per-dimension configuration. </summary>
+        public static readonly DimConfig DEFAULT_DIM_CONFIG = new DimConfig();
+
+        /// <summary>
+        /// Default constructor. </summary>
+        public FacetsConfig()
+        {
+        }
+
+        /// <summary>
+        /// Get the default configuration for new dimensions.  Useful when
+        ///  the dimension is not known beforehand and may need different 
+        ///  global default settings, like {@code multivalue =
+        ///  true}.
+        /// </summary>
+        ///  <returns> The default configuration to be used for dimensions that 
+        ///  are not yet set in the <seealso cref="FacetsConfig"/>  </returns>
+        protected virtual DimConfig DefaultDimConfig
+        {
+            get
+            {
+                return DEFAULT_DIM_CONFIG;
+            }
+        }
+
+        /// <summary>
+        /// Get the current configuration for a dimension. </summary>
+        public virtual DimConfig GetDimConfig(string dimName)
+        {
+            lock (this)
+            {
+                DimConfig ft;
+                if (!fieldTypes.TryGetValue(dimName,out ft))
+                {
+                    ft = DefaultDimConfig;
+                }
+                return ft;
+            }
+        }
+
+        /// <summary>
+        /// Pass {@code true} if this dimension is hierarchical
+        ///  (has depth > 1 paths). 
+        /// </summary>
+        public virtual void SetHierarchical(string dimName, bool v)
+        {
+            lock (this)
+            {
+                DimConfig ft;
+                if (!fieldTypes.TryGetValue(dimName,out ft))
+                {
+                    ft = new DimConfig();
+                    fieldTypes[dimName] = ft;
+                }
+                ft.hierarchical = v;
+            }
+        }
+
+        /// <summary>
+        /// Pass {@code true} if this dimension may have more than
+        ///  one value per document. 
+        /// </summary>
+        public virtual void SetMultiValued(string dimName, bool v)
+        {
+            lock (this)
+            {
+                DimConfig ft;
+                if (!fieldTypes.TryGetValue(dimName,out ft))
+                {
+                    ft = new DimConfig();
+                    fieldTypes[dimName] = ft;
+                }
+                ft.multiValued = v;
+            }
+        }
+
+        /// <summary>
+        /// Pass {@code true} if at search time you require
+        ///  accurate counts of the dimension, i.e. how many
+        ///  hits have this dimension. 
+        /// </summary>
+        public virtual void SetRequireDimCount(string dimName, bool v)
+        {
+            lock (this)
+            {
+                DimConfig ft;
+                if (!fieldTypes.TryGetValue(dimName,out ft))
+                {
+                    ft = new DimConfig();
+                    fieldTypes[dimName] = ft;
+                }
+                ft.requireDimCount = v;
+            }
+        }
+
+        /// <summary>
+        /// Specify which index field name should hold the
+        ///  ordinals for this dimension; this is only used by the
+        ///  taxonomy based facet methods. 
+        /// </summary>
+        public virtual void SetIndexFieldName(string dimName, string indexFieldName)
+        {
+            lock (this)
+            {
+                DimConfig ft;
+                if (!fieldTypes.TryGetValue(dimName,out ft))
+                {
+                    ft = new DimConfig();
+                    fieldTypes[dimName] = ft;
+                }
+                ft.indexFieldName = indexFieldName;
+            }
+        }
+
+        /// <summary>
+        /// Returns map of field name to <seealso cref="DimConfig"/>. </summary>
+        public virtual IDictionary<string, DimConfig> DimConfigs
+        {
+            get
+            {
+                return fieldTypes;
+            }
+        }
+
+        private static void CheckSeen(HashSet<string> seenDims, string dim)
+        {
+            if (seenDims.Contains(dim))
+            {
+                throw new System.ArgumentException("dimension \"" + dim + "\" is not multiValued, but it appears more than once in this document");
+            }
+            seenDims.Add(dim);
+        }
+
+        /// <summary>
+        /// Translates any added <seealso cref="FacetField"/>s into normal fields for indexing;
+        /// only use this version if you did not add any taxonomy-based fields (
+        /// <seealso cref="FacetField"/> or <seealso cref="AssociationFacetField"/>).
+        /// 
+        /// <para>
+        /// <b>NOTE:</b> you should add the returned document to IndexWriter, not the
+        /// input one!
+        /// </para>
+        /// </summary>
+        public virtual Document Build(Document doc)
+        {
+            return Build(null, doc);
+        }
+
+        /// <summary>
+        /// Translates any added <seealso cref="FacetField"/>s into normal fields for indexing.
+        /// 
+        /// <para>
+        /// <b>NOTE:</b> you should add the returned document to IndexWriter, not the
+        /// input one!
+        /// </para>
+        /// </summary>
+        public virtual Document Build(TaxonomyWriter taxoWriter, Document doc)
+        {
+            // Find all FacetFields, collated by the actual field:
+            IDictionary<string, IList<FacetField>> byField = new Dictionary<string, IList<FacetField>>();
+
+            // ... and also all SortedSetDocValuesFacetFields:
+            IDictionary<string, IList<SortedSetDocValuesFacetField>> dvByField = new Dictionary<string, IList<SortedSetDocValuesFacetField>>();
+
+            // ... and also all AssociationFacetFields
+            IDictionary<string, IList<AssociationFacetField>> assocByField = new Dictionary<string, IList<AssociationFacetField>>();
+
+            var seenDims = new HashSet<string>();
+
+            foreach (IndexableField field in doc.Fields)
+            {
+                if (field.FieldType() == FacetField.TYPE)
+                {
+                    FacetField facetField = (FacetField)field;
+                    FacetsConfig.DimConfig dimConfig = GetDimConfig(facetField.dim);
+                    if (dimConfig.multiValued == false)
+                    {
+                        CheckSeen(seenDims, facetField.dim);
+                    }
+                    string indexFieldName = dimConfig.indexFieldName;
+                    IList<FacetField> fields; 
+                    if (!byField.TryGetValue(indexFieldName,out fields))
+                    {
+                        fields = new List<FacetField>();
+                        byField[indexFieldName] = fields;
+                    }
+                    fields.Add(facetField);
+                }
+
+                if (field.FieldType() == SortedSetDocValuesFacetField.TYPE)
+                {
+                    var facetField = (SortedSetDocValuesFacetField)field;
+                    FacetsConfig.DimConfig dimConfig = GetDimConfig(facetField.Dim);
+                    if (dimConfig.multiValued == false)
+                    {
+                        CheckSeen(seenDims, facetField.Dim);
+                    }
+                    string indexFieldName = dimConfig.indexFieldName;
+                    IList<SortedSetDocValuesFacetField> fields;
+                    if (!dvByField.TryGetValue(indexFieldName,out fields))
+                    {
+                        fields = new List<SortedSetDocValuesFacetField>();
+                        dvByField[indexFieldName] = fields;
+                    }
+                    fields.Add(facetField);
+                }
+
+                if (field.FieldType() == AssociationFacetField.TYPE)
+                {
+                    AssociationFacetField facetField = (AssociationFacetField)field;
+                    FacetsConfig.DimConfig dimConfig = GetDimConfig(facetField.dim);
+                    if (dimConfig.multiValued == false)
+                    {
+                        CheckSeen(seenDims, facetField.dim);
+                    }
+                    if (dimConfig.hierarchical)
+                    {
+                        throw new System.ArgumentException("AssociationFacetField cannot be hierarchical (dim=\"" + facetField.dim + "\")");
+                    }
+                    if (dimConfig.requireDimCount)
+                    {
+                        throw new System.ArgumentException("AssociationFacetField cannot requireDimCount (dim=\"" + facetField.dim + "\")");
+                    }
+
+                    string indexFieldName = dimConfig.indexFieldName;
+                    IList<AssociationFacetField> fields;
+                    if (!assocByField.TryGetValue(indexFieldName,out fields))
+                    {
+                        fields = new List<AssociationFacetField>();
+                        assocByField[indexFieldName] = fields;
+                    }
+                    fields.Add(facetField);
+
+                    // Best effort: detect mis-matched types in same
+                    // indexed field:
+                    string type;
+                    if (facetField is IntAssociationFacetField)
+                    {
+                        type = "int";
+                    }
+                    else if (facetField is FloatAssociationFacetField)
+                    {
+                        type = "float";
+                    }
+                    else
+                    {
+                        type = "bytes";
+                    }
+                    // NOTE: not thread safe, but this is just best effort:
+                    string curType;
+                    if (!assocDimTypes.TryGetValue(indexFieldName,out curType))
+                    {
+                        assocDimTypes[indexFieldName] = type;
+                    }
+                    else if (!curType.Equals(type))
+                    {
+                        throw new System.ArgumentException("mixing incompatible types of AssocationFacetField (" + curType + " and " + type + ") in indexed field \"" + indexFieldName + "\"; use FacetsConfig to change the indexFieldName for each dimension");
+                    }
+                }
+            }
+
+            Document result = new Document();
+
+            ProcessFacetFields(taxoWriter, byField, result);
+            processSSDVFacetFields(dvByField, result);
+            ProcessAssocFacetFields(taxoWriter, assocByField, result);
+
+            //System.out.println("add stored: " + addedStoredFields);
+
+            foreach (IndexableField field in doc.Fields)
+            {
+                IndexableFieldType ft = field.FieldType();
+                if (ft != FacetField.TYPE && ft != SortedSetDocValuesFacetField.TYPE && ft != AssociationFacetField.TYPE)
+                {
+                    result.Add(field);
+                }
+            }
+
+            return result;
+        }
+
+        private void ProcessFacetFields(TaxonomyWriter taxoWriter, IDictionary<string, IList<FacetField>> byField, Document doc)
+        {
+
+            foreach (KeyValuePair<string, IList<FacetField>> ent in byField)
+            {
+
+                string indexFieldName = ent.Key;
+                //System.out.println("  indexFieldName=" + indexFieldName + " fields=" + ent.getValue());
+
+                IntsRef ordinals = new IntsRef(32);
+                foreach (FacetField facetField in ent.Value)
+                {
+
+                    FacetsConfig.DimConfig ft = GetDimConfig(facetField.dim);
+                    if (facetField.path.Length > 1 && ft.hierarchical == false)
+                    {
+                        throw new System.ArgumentException("dimension \"" + facetField.dim + "\" is not hierarchical yet has " + facetField.path.Length + " components");
+                    }
+
+                    FacetLabel cp = new FacetLabel(facetField.dim, facetField.path);
+
+                    checkTaxoWriter(taxoWriter);
+                    int ordinal = taxoWriter.AddCategory(cp);
+                    if (ordinals.Length == ordinals.Ints.Length)
+                    {
+                        ordinals.Grow(ordinals.Length + 1);
+                    }
+                    ordinals.Ints[ordinals.Length++] = ordinal;
+                    //System.out.println("ords[" + (ordinals.length-1) + "]=" + ordinal);
+                    //System.out.println("  add cp=" + cp);
+
+                    if (ft.multiValued && (ft.hierarchical || ft.requireDimCount))
+                    {
+                        //System.out.println("  add parents");
+                        // Add all parents too:
+                        int parent = taxoWriter.GetParent(ordinal);
+                        while (parent > 0)
+                        {
+                            if (ordinals.Ints.Length == ordinals.Length)
+                            {
+                                ordinals.Grow(ordinals.Length + 1);
+                            }
+                            ordinals.Ints[ordinals.Length++] = parent;
+                            parent = taxoWriter.GetParent(parent);
+                        }
+
+                        if (ft.requireDimCount == false)
+                        {
+                            // Remove last (dimension) ord:
+                            ordinals.Length--;
+                        }
+                    }
+
+                    // Drill down:
+                    for (int i = 1; i <= cp.length; i++)
+                    {
+                        doc.Add(new StringField(indexFieldName, PathToString(cp.components, i), Field.Store.NO));
+                    }
+                }
+
+                // Facet counts:
+                // DocValues are considered stored fields:
+                doc.Add(new BinaryDocValuesField(indexFieldName, DedupAndEncode(ordinals)));
+            }
+        }
+
+        public void processSSDVFacetFields(IDictionary<string, IList<SortedSetDocValuesFacetField>> byField, Document doc)
+        {
+            //System.out.println("process SSDV: " + byField);
+            foreach (KeyValuePair<string, IList<SortedSetDocValuesFacetField>> ent in byField)
+            {
+
+                string indexFieldName = ent.Key;
+                //System.out.println("  field=" + indexFieldName);
+
+                foreach (SortedSetDocValuesFacetField facetField in ent.Value)
+                {
+                    FacetLabel cp = new FacetLabel(facetField.Dim, facetField.Label);
+                    string fullPath = PathToString(cp.components, cp.length);
+                    //System.out.println("add " + fullPath);
+
+                    // For facet counts:
+                    doc.Add(new SortedSetDocValuesField(indexFieldName, new BytesRef(fullPath)));
+
+                    // For drill-down:
+                    doc.Add(new StringField(indexFieldName, fullPath, Field.Store.NO));
+                    doc.Add(new StringField(indexFieldName, facetField.Dim, Field.Store.NO));
+                }
+            }
+        }
+
+        private void ProcessAssocFacetFields(TaxonomyWriter taxoWriter, IDictionary<string, IList<AssociationFacetField>> byField, Document doc)
+        {
+            foreach (KeyValuePair<string, IList<AssociationFacetField>> ent in byField)
+            {
+                sbyte[] bytes = new sbyte[16];
+                int upto = 0;
+                string indexFieldName = ent.Key;
+                foreach (AssociationFacetField field in ent.Value)
+                {
+                    // NOTE: we don't add parents for associations
+                    checkTaxoWriter(taxoWriter);
+                    FacetLabel label = new FacetLabel(field.dim, field.path);
+                    int ordinal = taxoWriter.AddCategory(label);
+                    if (upto + 4 > bytes.Length)
+                    {
+                        bytes = ArrayUtil.Grow(bytes, upto + 4);
+                    }
+                    // big-endian:
+                    bytes[upto++] = (sbyte)(ordinal >> 24);
+                    bytes[upto++] = (sbyte)(ordinal >> 16);
+                    bytes[upto++] = (sbyte)(ordinal >> 8);
+                    bytes[upto++] = (sbyte)ordinal;
+                    if (upto + field.assoc.Length > bytes.Length)
+                    {
+                        bytes = ArrayUtil.Grow(bytes, upto + field.assoc.Length);
+                    }
+                    Array.Copy(field.assoc.Bytes, field.assoc.Offset, bytes, upto, field.assoc.Length);
+                    upto += field.assoc.Length;
+
+                    // Drill down:
+                    for (int i = 1; i <= label.length; i++)
+                    {
+                        doc.Add(new StringField(indexFieldName, PathToString(label.components, i), Field.Store.NO));
+                    }
+                }
+                doc.Add(new BinaryDocValuesField(indexFieldName, new BytesRef(bytes, 0, upto)));
+            }
+        }
+
+        /// <summary>
+        /// Encodes ordinals into a BytesRef; expert: subclass can
+        ///  override this to change encoding. 
+        /// </summary>
+        protected internal virtual BytesRef DedupAndEncode(IntsRef ordinals)
+        {
+            Array.Sort(ordinals.Ints, ordinals.Offset, ordinals.Length);
+            sbyte[] bytes = new sbyte[5 * ordinals.Length];
+            int lastOrd = -1;
+            int upto = 0;
+            for (int i = 0; i < ordinals.Length; i++)
+            {
+                int ord = ordinals.Ints[ordinals.Offset + i];
+                // ord could be == lastOrd, so we must dedup:
+                if (ord > lastOrd)
+                {
+                    int delta;
+                    if (lastOrd == -1)
+                    {
+                        delta = ord;
+                    }
+                    else
+                    {
+                        delta = ord - lastOrd;
+                    }
+                    if ((delta & ~0x7F) == 0)
+                    {
+                        bytes[upto] = (sbyte)delta;
+                        upto++;
+                    }
+                    else if ((delta & ~0x3FFF) == 0)
+                    {
+                        bytes[upto] = unchecked((sbyte)(0x80 | ((delta & 0x3F80) >> 7)));
+                        bytes[upto + 1] = (sbyte)(delta & 0x7F);
+                        upto += 2;
+                    }
+                    else if ((delta & ~0x1FFFFF) == 0)
+                    {
+                        bytes[upto] = unchecked((sbyte)(0x80 | ((delta & 0x1FC000) >> 14)));
+                        bytes[upto + 1] = unchecked((sbyte)(0x80 | ((delta & 0x3F80) >> 7)));
+                        bytes[upto + 2] = (sbyte)(delta & 0x7F);
+                        upto += 3;
+                    }
+                    else if ((delta & ~0xFFFFFFF) == 0)
+                    {
+                        bytes[upto] = unchecked((sbyte)(0x80 | ((delta & 0xFE00000) >> 21)));
+                        bytes[upto + 1] = unchecked((sbyte)(0x80 | ((delta & 0x1FC000) >> 14)));
+                        bytes[upto + 2] = unchecked((sbyte)(0x80 | ((delta & 0x3F80) >> 7)));
+                        bytes[upto + 3] = (sbyte)(delta & 0x7F);
+                        upto += 4;
+                    }
+                    else
+                    {
+                        bytes[upto] = unchecked((sbyte)(0x80 | ((delta & 0xF0000000) >> 28)));
+                        bytes[upto + 1] = unchecked((sbyte)(0x80 | ((delta & 0xFE00000) >> 21)));
+                        bytes[upto + 2] = unchecked((sbyte)(0x80 | ((delta & 0x1FC000) >> 14)));
+                        bytes[upto + 3] = unchecked((sbyte)(0x80 | ((delta & 0x3F80) >> 7)));
+                        bytes[upto + 4] = (sbyte)(delta & 0x7F);
+                        upto += 5;
+                    }
+                    lastOrd = ord;
+                }
+            }
+            return new BytesRef(bytes, 0, upto);
+        }
+
+        private void checkTaxoWriter(TaxonomyWriter taxoWriter)
+        {
+            if (taxoWriter == null)
+            {
+                throw new ThreadStateException("a non-null TaxonomyWriter must be provided when indexing FacetField or AssociationFacetField");
+            }
+        }
+
+        // Joins the path components together:
+        private const char DELIM_CHAR = '\u001F';
+
+        // Escapes any occurrence of the path component inside the label:
+        private const char ESCAPE_CHAR = '\u001E';
+
+        /// <summary>
+        /// Turns a dim + path into an encoded string. </summary>
+        public static string PathToString(string dim, string[] path)
+        {
+            string[] fullPath = new string[1 + path.Length];
+            fullPath[0] = dim;
+            Array.Copy(path, 0, fullPath, 1, path.Length);
+            return PathToString(fullPath, fullPath.Length);
+        }
+
+        /// <summary>
+        /// Turns a dim + path into an encoded string. </summary>
+        public static string PathToString(string[] path)
+        {
+            return PathToString(path, path.Length);
+        }
+
+        /// <summary>
+        /// Turns the first {@code length} elements of {@code
+        /// path} into an encoded string. 
+        /// </summary>
+        public static string PathToString(string[] path, int length)
+        {
+            if (length == 0)
+            {
+                return "";
+            }
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < length; i++)
+            {
+                string s = path[i];
+                if (s.Length == 0)
+                {
+                    throw new System.ArgumentException("each path component must have length > 0 (got: \"\")");
+                }
+                int numChars = s.Length;
+                for (int j = 0; j < numChars; j++)
+                {
+                    char ch = s[j];
+                    if (ch == DELIM_CHAR || ch == ESCAPE_CHAR)
+                    {
+                        sb.Append(ESCAPE_CHAR);
+                    }
+                    sb.Append(ch);
+                }
+                sb.Append(DELIM_CHAR);
+            }
+
+            // Trim off last DELIM_CHAR:
+            sb.Length = sb.Length - 1;
+            return sb.ToString();
+        }
+
+        /// <summary>
+        /// Turns an encoded string (from a previous call to {@link
+        ///  #pathToString}) back into the original {@code
+        ///  String[]}. 
+        /// </summary>
+        public static string[] StringToPath(string s)
+        {
+            IList<string> parts = new List<string>();
+            int length = s.Length;
+            if (length == 0)
+            {
+                return new string[0];
+            }
+            char[] buffer = new char[length];
+
+            int upto = 0;
+            bool lastEscape = false;
+            for (int i = 0; i < length; i++)
+            {
+                char ch = s[i];
+                if (lastEscape)
+                {
+                    buffer[upto++] = ch;
+                    lastEscape = false;
+                }
+                else if (ch == ESCAPE_CHAR)
+                {
+                    lastEscape = true;
+                }
+                else if (ch == DELIM_CHAR)
+                {
+                    parts.Add(new string(buffer, 0, upto));
+                    upto = 0;
+                }
+                else
+                {
+                    buffer[upto++] = ch;
+                }
+            }
+            parts.Add(new string(buffer, 0, upto));
+            Debug.Assert(!lastEscape);
+            return parts.ToArray();
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/LabelAndValue.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/LabelAndValue.cs b/Lucene.Net.Facet/LabelAndValue.cs
new file mode 100644
index 0000000..0cbcddc
--- /dev/null
+++ b/Lucene.Net.Facet/LabelAndValue.cs
@@ -0,0 +1,66 @@
+using Lucene.Net.Support;
+
+namespace Lucene.Net.Facet
+{
+
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// Single label and its value, usually contained in a
+    ///  <seealso cref="FacetResult"/>. 
+    /// </summary>
+    public sealed class LabelAndValue
+    {
+        /// <summary>
+        /// Facet's label. </summary>
+        public readonly string label;
+
+        /// <summary>
+        /// Value associated with this label. </summary>
+        public readonly float value;
+
+        /// <summary>
+        /// Sole constructor. </summary>
+        public LabelAndValue(string label, float value)
+        {
+            this.label = label;
+            this.value = value;
+        }
+
+        public override string ToString()
+        {
+            return label + " (" + value + ")";
+        }
+
+        public override bool Equals(object _other)
+        {
+            if ((_other is LabelAndValue) == false)
+            {
+                return false;
+            }
+            LabelAndValue other = (LabelAndValue)_other;
+            return label.Equals(other.label) && value.Equals(other.value);
+        }
+
+        public override int GetHashCode()
+        {
+            return label.GetHashCode() + 1439 * value.GetHashCode();
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Lucene.Net.Facet.csproj
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Lucene.Net.Facet.csproj b/Lucene.Net.Facet/Lucene.Net.Facet.csproj
new file mode 100644
index 0000000..f7a60f2
--- /dev/null
+++ b/Lucene.Net.Facet/Lucene.Net.Facet.csproj
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{48F7884A-9454-4E88-8413-9D35992CB440}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>Lucene.Net.Facet</RootNamespace>
+    <AssemblyName>Lucene.Net.Facet</AssemblyName>
+    <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="DrillDownQuery.cs" />
+    <Compile Include="DrillSideways.cs" />
+    <Compile Include="DrillSidewaysQuery.cs" />
+    <Compile Include="DrillSidewaysScorer.cs" />
+    <Compile Include="FacetField.cs" />
+    <Compile Include="FacetResult.cs" />
+    <Compile Include="Facets.cs" />
+    <Compile Include="FacetsCollector.cs" />
+    <Compile Include="FacetsConfig.cs" />
+    <Compile Include="LabelAndValue.cs" />
+    <Compile Include="MultiFacets.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="RandomSamplingFacetsCollector.cs" />
+    <Compile Include="Range\DoubleRange.cs" />
+    <Compile Include="Range\DoubleRangeFacetCounts.cs" />
+    <Compile Include="Range\LongRange.cs" />
+    <Compile Include="Range\LongRangeCounter.cs" />
+    <Compile Include="Range\LongRangeFacetCounts.cs" />
+    <Compile Include="Range\Range.cs" />
+    <Compile Include="Range\RangeFacetCounts.cs" />
+    <Compile Include="SortedSet\DefaultSortedSetDocValuesReaderState.cs" />
+    <Compile Include="SortedSet\SortedSetDocValuesFacetCounts.cs" />
+    <Compile Include="SortedSet\SortedSetDocValuesFacetField.cs" />
+    <Compile Include="SortedSet\SortedSetDocValuesReaderState.cs" />
+    <Compile Include="Taxonomy\AssociationFacetField.cs" />
+    <Compile Include="Taxonomy\CachedOrdinalsReader.cs" />
+    <Compile Include="Taxonomy\CategoryPath.cs" />
+    <Compile Include="Taxonomy\Directory\Consts.cs" />
+    <Compile Include="Taxonomy\Directory\DirectoryTaxonomyReader.cs" />
+    <Compile Include="Taxonomy\Directory\DirectoryTaxonomyWriter.cs" />
+    <Compile Include="Taxonomy\Directory\TaxonomyIndexArrays.cs" />
+    <Compile Include="Taxonomy\DocValuesOrdinalsReader.cs" />
+    <Compile Include="Taxonomy\FacetLabel.cs" />
+    <Compile Include="Taxonomy\FastTaxonomyFacetCounts.cs" />
+    <Compile Include="Taxonomy\FloatAssociationFacetField.cs" />
+    <Compile Include="Taxonomy\FloatTaxonomyFacets.cs" />
+    <Compile Include="Taxonomy\IntAssociationFacetField.cs" />
+    <Compile Include="Taxonomy\IntTaxonomyFacets.cs" />
+    <Compile Include="Taxonomy\LRUHashMap.cs" />
+    <Compile Include="Taxonomy\OrdinalsReader.cs" />
+    <Compile Include="Taxonomy\ParallelTaxonomyArrays.cs" />
+    <Compile Include="Taxonomy\PrintTaxonomyStats.cs" />
+    <Compile Include="Taxonomy\SearcherTaxonomyManager.cs" />
+    <Compile Include="Taxonomy\TaxonomyFacetCounts.cs" />
+    <Compile Include="Taxonomy\TaxonomyFacets.cs" />
+    <Compile Include="Taxonomy\TaxonomyFacetSumFloatAssociations.cs" />
+    <Compile Include="Taxonomy\TaxonomyFacetSumIntAssociations.cs" />
+    <Compile Include="Taxonomy\TaxonomyFacetSumValueSource.cs" />
+    <Compile Include="Taxonomy\TaxonomyReader.cs" />
+    <Compile Include="Taxonomy\TaxonomyWriter.cs" />
+    <Compile Include="Taxonomy\WriterCache\CategoryPathUtils.cs" />
+    <Compile Include="Taxonomy\WriterCache\CharBlockArray.cs" />
+    <Compile Include="Taxonomy\WriterCache\Cl2oTaxonomyWriterCache.cs" />
+    <Compile Include="Taxonomy\WriterCache\CollisionMap.cs" />
+    <Compile Include="Taxonomy\WriterCache\CompactLabelToOrdinal.cs" />
+    <Compile Include="Taxonomy\WriterCache\LabelToOrdinal.cs" />
+    <Compile Include="Taxonomy\WriterCache\LruTaxonomyWriterCache.cs" />
+    <Compile Include="Taxonomy\WriterCache\NameHashIntCacheLRU.cs" />
+    <Compile Include="Taxonomy\WriterCache\NameIntCacheLRU.cs" />
+    <Compile Include="Taxonomy\WriterCache\TaxonomyWriterCache.cs" />
+    <Compile Include="TopOrdAndFloatQueue.cs" />
+    <Compile Include="TopOrdAndIntQueue.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\src\Lucene.Net.Core\Lucene.Net.csproj">
+      <Project>{5d4ad9be-1ffb-41ab-9943-25737971bf57}</Project>
+      <Name>Lucene.Net</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\src\Lucene.Net.Queries\Lucene.Net.Queries.csproj">
+      <Project>{69d7956c-c2cc-4708-b399-a188fec384c4}</Project>
+      <Name>Lucene.Net.Queries</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/MultiFacets.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/MultiFacets.cs b/Lucene.Net.Facet/MultiFacets.cs
new file mode 100644
index 0000000..b980a5d
--- /dev/null
+++ b/Lucene.Net.Facet/MultiFacets.cs
@@ -0,0 +1,103 @@
+using System.Collections.Generic;
+using Lucene.Net.Facet;
+using Lucene.Net.Support;
+
+namespace Lucene.Net.Facet
+{
+
+    /*
+     * 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.
+     */
+
+
+    /// <summary>
+    /// Maps specified dims to provided Facets impls; else, uses
+    ///  the default Facets impl. 
+    /// </summary>
+    public class MultiFacets : Facets
+    {
+        private readonly IDictionary<string, Facets> dimToFacets;
+        private readonly Facets defaultFacets;
+
+        /// <summary>
+        /// Create this, with the specified default <seealso cref="Facets"/>
+        ///  for fields not included in {@code dimToFacets}. 
+        /// </summary>
+        public MultiFacets(IDictionary<string, Facets> dimToFacets, Facets defaultFacets = null)
+        {
+            this.dimToFacets = dimToFacets;
+            this.defaultFacets = defaultFacets;
+        }
+
+        public override FacetResult GetTopChildren(int topN, string dim, params string[] path)
+        {
+            Facets facets = dimToFacets[dim];
+            if (facets == null)
+            {
+                if (defaultFacets == null)
+                {
+                    throw new System.ArgumentException("invalid dim \"" + dim + "\"");
+                }
+                facets = defaultFacets;
+            }
+            return facets.GetTopChildren(topN, dim, path);
+        }
+
+        
+        public override float GetSpecificValue(string dim, params string[] path)
+        {
+            Facets facets;
+            if (!dimToFacets.TryGetValue(dim,out facets))
+            {
+                if (defaultFacets == null)
+                {
+                    throw new System.ArgumentException("invalid dim \"" + dim + "\"");
+                }
+                facets = defaultFacets;
+            }
+            return facets.GetSpecificValue(dim, path);
+        }
+
+        public override IList<FacetResult> GetAllDims(int topN)
+        {
+
+            IList<FacetResult> results = new List<FacetResult>();
+
+            // First add the specific dim's facets:
+            foreach (KeyValuePair<string, Facets> ent in dimToFacets)
+            {
+                results.Add(ent.Value.GetTopChildren(topN, ent.Key));
+            }
+
+            if (defaultFacets != null)
+            {
+
+                // Then add all default facets as long as we didn't
+                // already add that dim:
+                foreach (FacetResult result in defaultFacets.GetAllDims(topN))
+                {
+                    if (dimToFacets.ContainsKey(result.dim) == false)
+                    {
+                        results.Add(result);
+                    }
+                }
+            }
+
+            return results;
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Properties/AssemblyInfo.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Properties/AssemblyInfo.cs b/Lucene.Net.Facet/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..741693d
--- /dev/null
+++ b/Lucene.Net.Facet/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Lucene.Net.Facet")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Lucene.Net.Facet")]
+[assembly: AssemblyCopyright("Copyright ©  2014")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("8dd11ab2-c5b3-4691-99da-2941f27e0e10")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers 
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/RandomSamplingFacetsCollector.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/RandomSamplingFacetsCollector.cs b/Lucene.Net.Facet/RandomSamplingFacetsCollector.cs
new file mode 100644
index 0000000..02a4d9e
--- /dev/null
+++ b/Lucene.Net.Facet/RandomSamplingFacetsCollector.cs
@@ -0,0 +1,323 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Lucene.Net.Facet;
+using Lucene.Net.Search;
+
+namespace Lucene.Net.Facet
+{
+
+    /*
+     * 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 DimConfig = FacetsConfig.DimConfig;
+    using IndexReader = Lucene.Net.Index.IndexReader;
+    using Term = Lucene.Net.Index.Term;
+    using DocIdSetIterator = Lucene.Net.Search.DocIdSetIterator;
+    using IndexSearcher = Lucene.Net.Search.IndexSearcher;
+    using FixedBitSet = Lucene.Net.Util.FixedBitSet;
+
+    /// <summary>
+    /// Collects hits for subsequent faceting, using sampling if needed. Once you've
+    /// run a search and collect hits into this, instantiate one of the
+    /// <seealso cref="Facets"/> subclasses to do the facet counting. Note that this collector
+    /// does not collect the scores of matching docs (i.e.
+    /// <seealso cref="FacetsCollector.MatchingDocs#scores"/>) is {@code null}.
+    /// <para>
+    /// If you require the original set of hits, you can call
+    /// <seealso cref="#getOriginalMatchingDocs()"/>. Also, since the counts of the top-facets
+    /// is based on the sampled set, you can amortize the counts by calling
+    /// <seealso cref="#amortizeFacetCounts"/>.
+    /// </para>
+    /// </summary>
+    public class RandomSamplingFacetsCollector : FacetsCollector
+    {
+
+        /// <summary>
+        /// Faster alternative for java.util.Random, inspired by
+        /// http://dmurphy747.wordpress.com/2011/03/23/xorshift-vs-random-
+        /// performance-in-java/
+        /// <para>
+        /// Has a period of 2^64-1
+        /// </para>
+        /// </summary>
+        private class XORShift64Random
+        {
+
+            internal long x;
+
+            /// <summary>
+            /// Creates a xorshift random generator using the provided seed </summary>
+            public XORShift64Random(long seed)
+            {
+                x = seed == 0 ? 0xdeadbeef : seed;
+            }
+
+            /// <summary>
+            /// Get the next random long value </summary>
+            public virtual long RandomLong()
+            {
+                x ^= (x << 21);
+                x ^= ((long)((ulong)x >> 35));
+                x ^= (x << 4);
+                return x;
+            }
+
+            /// <summary>
+            /// Get the next random int, between 0 (inclusive) and n (exclusive) </summary>
+            public virtual int NextInt(int n)
+            {
+                int res = (int)(RandomLong() % n);
+                return (res < 0) ? -res : res;
+            }
+
+        }
+
+        private const int NOT_CALCULATED = -1;
+
+        private readonly int sampleSize;
+        private readonly XORShift64Random random;
+
+        private double samplingRate;
+        private IList<MatchingDocs> sampledDocs;
+        private int totalHits = NOT_CALCULATED;
+        private int leftoverBin = NOT_CALCULATED;
+        private int leftoverIndex = NOT_CALCULATED;
+
+        /// <summary>
+        /// Constructor with the given sample size and default seed.
+        /// </summary>
+        /// <seealso cref= #RandomSamplingFacetsCollector(int, long) </seealso>
+        public RandomSamplingFacetsCollector(int sampleSize)
+            : this(sampleSize, 0)
+        {
+        }
+
+        /// <summary>
+        /// Constructor with the given sample size and seed.
+        /// </summary>
+        /// <param name="sampleSize">
+        ///          The preferred sample size. If the number of hits is greater than
+        ///          the size, sampling will be done using a sample ratio of sampling
+        ///          size / totalN. For example: 1000 hits, sample size = 10 results in
+        ///          samplingRatio of 0.01. If the number of hits is lower, no sampling
+        ///          is done at all </param>
+        /// <param name="seed">
+        ///          The random seed. If {@code 0} then a seed will be chosen for you. </param>
+        public RandomSamplingFacetsCollector(int sampleSize, long seed)
+            : base(false)
+        {
+            this.sampleSize = sampleSize;
+            this.random = new XORShift64Random(seed);
+            this.sampledDocs = null;
+        }
+
+        /// <summary>
+        /// Returns the sampled list of the matching documents. Note that a
+        /// <seealso cref="FacetsCollector.MatchingDocs"/> instance is returned per segment, even
+        /// if no hits from that segment are included in the sampled set.
+        /// <para>
+        /// Note: One or more of the MatchingDocs might be empty (not containing any
+        /// hits) as result of sampling.
+        /// </para>
+        /// <para>
+        /// Note: {@code MatchingDocs.totalHits} is copied from the original
+        /// MatchingDocs, scores is set to {@code null}
+        /// </para>
+        /// </summary>
+        public override IList<MatchingDocs> GetMatchingDocs
+        {
+            get
+            {
+                IList<MatchingDocs> matchingDocs = base.GetMatchingDocs;
+
+                if (totalHits == NOT_CALCULATED)
+                {
+                    totalHits = 0;
+                    foreach (MatchingDocs md in matchingDocs)
+                    {
+                        totalHits += md.totalHits;
+                    }
+                }
+
+                if (totalHits <= sampleSize)
+                {
+                    return matchingDocs;
+                }
+
+                if (sampledDocs == null)
+                {
+                    samplingRate = (1.0 * sampleSize) / totalHits;
+                    sampledDocs = CreateSampledDocs(matchingDocs);
+                }
+                return sampledDocs;
+            }
+        }
+
+        /// <summary>
+        /// Returns the original matching documents. </summary>
+        public virtual IList<MatchingDocs> OriginalMatchingDocs
+        {
+            get
+            {
+                return base.GetMatchingDocs;
+            }
+        }
+
+        /// <summary>
+        /// Create a sampled copy of the matching documents list. </summary>
+        private IList<MatchingDocs> CreateSampledDocs(IList<MatchingDocs> matchingDocsList)
+        {
+            IList<MatchingDocs> sampledDocsList = new List<MatchingDocs>(matchingDocsList.Count);
+            foreach (MatchingDocs docs in matchingDocsList)
+            {
+                sampledDocsList.Add(CreateSample(docs));
+            }
+            return sampledDocsList;
+        }
+
+        /// <summary>
+        /// Create a sampled of the given hits. </summary>
+        private MatchingDocs CreateSample(MatchingDocs docs)
+        {
+            int maxdoc = docs.context.Reader.MaxDoc;
+
+            // TODO: we could try the WAH8DocIdSet here as well, as the results will be sparse
+            FixedBitSet sampleDocs = new FixedBitSet(maxdoc);
+
+            int binSize = (int)(1.0 / samplingRate);
+
+            try
+            {
+                int counter = 0;
+                int limit, randomIndex;
+                if (leftoverBin != NOT_CALCULATED)
+                {
+                    limit = leftoverBin;
+                    // either NOT_CALCULATED, which means we already sampled from that bin,
+                    // or the next document to sample
+                    randomIndex = leftoverIndex;
+                }
+                else
+                {
+                    limit = binSize;
+                    randomIndex = random.NextInt(binSize);
+                }
+                DocIdSetIterator it = docs.bits.GetIterator();
+                for (int doc = it.NextDoc(); doc != DocIdSetIterator.NO_MORE_DOCS; doc = it.NextDoc())
+                {
+                    if (counter == randomIndex)
+                    {
+                        sampleDocs.Set(doc);
+                    }
+                    counter++;
+                    if (counter >= limit)
+                    {
+                        counter = 0;
+                        limit = binSize;
+                        randomIndex = random.NextInt(binSize);
+                    }
+                }
+
+                if (counter == 0)
+                {
+                    // we either exhausted the bin and the iterator at the same time, or
+                    // this segment had no results. in the latter case we might want to
+                    // carry leftover to the next segment as is, but that complicates the
+                    // code and doesn't seem so important.
+                    leftoverBin = leftoverIndex = NOT_CALCULATED;
+                }
+                else
+                {
+                    leftoverBin = limit - counter;
+                    if (randomIndex > counter)
+                    {
+                        // the document to sample is in the next bin
+                        leftoverIndex = randomIndex - counter;
+                    }
+                    else if (randomIndex < counter)
+                    {
+                        // we sampled a document from the bin, so just skip over remaining
+                        // documents in the bin in the next segment.
+                        leftoverIndex = NOT_CALCULATED;
+                    }
+                }
+
+                return new MatchingDocs(docs.context, sampleDocs, docs.totalHits, null);
+            }
+            catch (IOException)
+            {
+                throw new Exception();
+            }
+        }
+
+        /// <summary>
+        /// Note: if you use a counting <seealso cref="Facets"/> implementation, you can amortize the
+        /// sampled counts by calling this method. Uses the <seealso cref="FacetsConfig"/> and
+        /// the <seealso cref="IndexSearcher"/> to determine the upper bound for each facet value.
+        /// </summary>
+        public virtual FacetResult AmortizeFacetCounts(FacetResult res, FacetsConfig config, IndexSearcher searcher)
+        {
+            if (res == null || totalHits <= sampleSize)
+            {
+                return res;
+            }
+
+            LabelAndValue[] fixedLabelValues = new LabelAndValue[res.labelValues.Length];
+            IndexReader reader = searcher.IndexReader;
+            DimConfig dimConfig = config.GetDimConfig(res.dim);
+
+            // +2 to prepend dimension, append child label
+            string[] childPath = new string[res.path.Length + 2];
+            childPath[0] = res.dim;
+
+            Array.Copy(res.path, 0, childPath, 1, res.path.Length); // reuse
+
+            for (int i = 0; i < res.labelValues.Length; i++)
+            {
+                childPath[res.path.Length + 1] = res.labelValues[i].label;
+                string fullPath = FacetsConfig.PathToString(childPath, childPath.Length);
+                int max = reader.DocFreq(new Term(dimConfig.indexFieldName, fullPath));
+                int correctedCount = (int)((double)res.labelValues[i].value / samplingRate);
+                correctedCount = Math.Min(max, correctedCount);
+                fixedLabelValues[i] = new LabelAndValue(res.labelValues[i].label, correctedCount);
+            }
+
+            // cap the total count on the total number of non-deleted documents in the reader
+            int correctedTotalCount = (int)res.value;
+            if (correctedTotalCount > 0)
+            {
+                correctedTotalCount = Math.Min(reader.NumDocs, (int)((double)res.value / samplingRate));
+            }
+
+            return new FacetResult(res.dim, res.path, correctedTotalCount, fixedLabelValues, res.childCount);
+        }
+
+        /// <summary>
+        /// Returns the sampling rate that was used. </summary>
+        public virtual double SamplingRate
+        {
+            get
+            {
+                return samplingRate;
+            }
+        }
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Range/DoubleRange.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Range/DoubleRange.cs b/Lucene.Net.Facet/Range/DoubleRange.cs
new file mode 100644
index 0000000..6e7e5ae
--- /dev/null
+++ b/Lucene.Net.Facet/Range/DoubleRange.cs
@@ -0,0 +1,242 @@
+using System;
+using System.Collections.Generic;
+using Lucene.Net.Support;
+
+namespace Lucene.Net.Facet.Range
+{
+
+    /*
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements.  See the NOTICE file distributed with
+     * this work for additional information regarding copyright ownership.
+     * The ASF licenses this file to You under the Apache License, Version 2.0
+     * (the "License"); you may not use this file except in compliance with
+     * the License.  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     */
+
+
+    using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext;
+    using FunctionValues = Lucene.Net.Queries.Function.FunctionValues;
+    using ValueSource = Lucene.Net.Queries.Function.ValueSource;
+    using DocIdSet = Lucene.Net.Search.DocIdSet;
+    using DocIdSetIterator = Lucene.Net.Search.DocIdSetIterator;
+    using Filter = Lucene.Net.Search.Filter;
+    using Bits = Lucene.Net.Util.Bits;
+    using NumericUtils = Lucene.Net.Util.NumericUtils;
+
+    /// <summary>
+    /// Represents a range over double values.
+    /// 
+    /// @lucene.experimental 
+    /// </summary>
+    public sealed class DoubleRange : Range
+    {
+        internal readonly double minIncl;
+        internal readonly double maxIncl;
+
+        /// <summary>
+        /// Minimum. </summary>
+        public readonly double min;
+
+        /// <summary>
+        /// Maximum. </summary>
+        public readonly double max;
+
+        /// <summary>
+        /// True if the minimum value is inclusive. </summary>
+        public readonly bool minInclusive;
+
+        /// <summary>
+        /// True if the maximum value is inclusive. </summary>
+        public readonly bool maxInclusive;
+
+        /// <summary>
+        /// Create a DoubleRange. </summary>
+        public DoubleRange(string label, double minIn, bool minInclusive, double maxIn, bool maxInclusive)
+            : base(label)
+        {
+            this.min = minIn;
+            this.max = maxIn;
+            this.minInclusive = minInclusive;
+            this.maxInclusive = maxInclusive;
+
+            // TODO: if DoubleDocValuesField used
+            // NumericUtils.doubleToSortableLong format (instead of
+            // Double.doubleToRawLongBits) we could do comparisons
+            // in long space 
+
+            if (double.IsNaN(min))
+            {
+                throw new System.ArgumentException("min cannot be NaN");
+            }
+            if (!minInclusive)
+            {
+                minIn += double.Epsilon;
+            }
+
+            if (double.IsNaN(max))
+            {
+                throw new System.ArgumentException("max cannot be NaN");
+            }
+            if (!maxInclusive)
+            {
+                // Why no Math.nextDown?
+                maxIn = maxIn -= double.Epsilon;
+            }
+
+            if (minIn > maxIn)
+            {
+                FailNoMatch();
+            }
+
+            this.minIncl = minIn;
+            this.maxIncl = maxIn;
+        }
+
+        /// <summary>
+        /// True if this range accepts the provided value. </summary>
+        public bool accept(double value)
+        {
+            return value >= minIncl && value <= maxIncl;
+        }
+
+        internal LongRange ToLongRange()
+        {
+            return new LongRange(Label, NumericUtils.DoubleToSortableLong(minIncl), true, NumericUtils.DoubleToSortableLong(maxIncl), true);
+        }
+
+        public override string ToString()
+        {
+            return "DoubleRange(" + minIncl + " to " + maxIncl + ")";
+        }
+
+        public override Filter GetFilter(Filter fastMatchFilter, ValueSource valueSource)
+        {
+            return new FilterAnonymousInnerClassHelper(this, fastMatchFilter, valueSource);
+        }
+
+        private class FilterAnonymousInnerClassHelper : Filter
+        {
+            private readonly DoubleRange outerInstance;
+
+            private Filter fastMatchFilter;
+            private ValueSource valueSource;
+
+            public FilterAnonymousInnerClassHelper(DoubleRange outerInstance, Filter fastMatchFilter, ValueSource valueSource)
+            {
+                this.outerInstance = outerInstance;
+                this.fastMatchFilter = fastMatchFilter;
+                this.valueSource = valueSource;
+            }
+
+
+            public override string ToString()
+            {
+                return "Filter(" + outerInstance.ToString() + ")";
+            }
+
+            public override DocIdSet GetDocIdSet(AtomicReaderContext context, Bits acceptDocs)
+            {
+
+                // TODO: this is just like ValueSourceScorer,
+                // ValueSourceFilter (spatial),
+                // ValueSourceRangeFilter (solr); also,
+                // https://issues.apache.org/jira/browse/LUCENE-4251
+
+                var values = valueSource.GetValues(new Dictionary<string,Lucene.Net.Search.Scorer>(), context);
+
+                int maxDoc = context.Reader.MaxDoc;
+
+                Bits fastMatchBits;
+                if (fastMatchFilter != null)
+                {
+                    DocIdSet dis = fastMatchFilter.GetDocIdSet(context, null);
+                    if (dis == null)
+                    {
+                        // No documents match
+                        return null;
+                    }
+                    fastMatchBits = dis.GetBits();
+                    if (fastMatchBits == null)
+                    {
+                        throw new System.ArgumentException("fastMatchFilter does not implement DocIdSet.bits");
+                    }
+                }
+                else
+                {
+                    fastMatchBits = null;
+                }
+
+                return new DocIdSetAnonymousInnerClassHelper(this, acceptDocs, values, maxDoc, fastMatchBits);
+            }
+
+            private class DocIdSetAnonymousInnerClassHelper : DocIdSet
+            {
+                private readonly FilterAnonymousInnerClassHelper outerInstance;
+
+                private Bits acceptDocs;
+                private FunctionValues values;
+                private int maxDoc;
+                private Bits fastMatchBits;
+
+                public DocIdSetAnonymousInnerClassHelper(FilterAnonymousInnerClassHelper outerInstance, Bits acceptDocs, FunctionValues values, int maxDoc, Bits fastMatchBits)
+                {
+                    this.outerInstance = outerInstance;
+                    this.acceptDocs = acceptDocs;
+                    this.values = values;
+                    this.maxDoc = maxDoc;
+                    this.fastMatchBits = fastMatchBits;
+                }
+
+
+                public override Bits GetBits()
+                {
+                    return new BitsAnonymousInnerClassHelper(this);
+                }
+
+                private class BitsAnonymousInnerClassHelper : Bits
+                {
+                    private readonly DocIdSetAnonymousInnerClassHelper outerInstance;
+
+                    public BitsAnonymousInnerClassHelper(DocIdSetAnonymousInnerClassHelper outerInstance)
+                    {
+                        this.outerInstance = outerInstance;
+                    }
+
+                    public virtual bool Get(int docID)
+                    {
+                        if (outerInstance.acceptDocs != null && outerInstance.acceptDocs.Get(docID) == false)
+                        {
+                            return false;
+                        }
+                        if (outerInstance.fastMatchBits != null && outerInstance.fastMatchBits.Get(docID) == false)
+                        {
+                            return false;
+                        }
+                        return outerInstance.outerInstance.outerInstance.accept(outerInstance.values.DoubleVal(docID));
+                    }
+
+                    public virtual int Length()
+                    {
+                        return outerInstance.maxDoc;
+                    }
+                }
+
+                public override DocIdSetIterator GetIterator()
+                {
+                    throw new System.NotSupportedException("this filter can only be accessed via bits()");
+                }
+            }
+        }
+    }
+
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Range/DoubleRangeFacetCounts.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Range/DoubleRangeFacetCounts.cs b/Lucene.Net.Facet/Range/DoubleRangeFacetCounts.cs
new file mode 100644
index 0000000..f6908dc
--- /dev/null
+++ b/Lucene.Net.Facet/Range/DoubleRangeFacetCounts.cs
@@ -0,0 +1,160 @@
+using System.Collections.Generic;
+using Lucene.Net.Facet;
+using Lucene.Net.Support;
+
+namespace Lucene.Net.Facet.Range
+{
+
+    /*
+     * 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 DoubleDocValuesField = Lucene.Net.Documents.DoubleDocValuesField; // javadocs
+    using FloatDocValuesField = Lucene.Net.Documents.FloatDocValuesField; // javadocs
+    using MatchingDocs = FacetsCollector.MatchingDocs;
+    using FunctionValues = Lucene.Net.Queries.Function.FunctionValues;
+    using ValueSource = Lucene.Net.Queries.Function.ValueSource;
+    using DoubleFieldSource = Lucene.Net.Queries.Function.ValueSources.DoubleFieldSource;
+    using FloatFieldSource = Lucene.Net.Queries.Function.ValueSources.FloatFieldSource; // javadocs
+    using DocIdSet = Lucene.Net.Search.DocIdSet;
+    using Filter = Lucene.Net.Search.Filter;
+    using Bits = Lucene.Net.Util.Bits;
+    using DocIdSetIterator = Lucene.Net.Search.DocIdSetIterator;
+    using NumericUtils = Lucene.Net.Util.NumericUtils;
+
+    /// <summary>
+    /// <seealso cref="Facets"/> implementation that computes counts for
+    ///  dynamic double ranges from a provided {@link
+    ///  ValueSource}, using <seealso cref="FunctionValues#doubleVal"/>.  Use
+    ///  this for dimensions that change in real-time (e.g. a
+    ///  relative time based dimension like "Past day", "Past 2
+    ///  days", etc.) or that change for each request (e.g.
+    ///  distance from the user's location, "< 1 km", "< 2 km",
+    ///  etc.).
+    /// 
+    ///  <para> If you had indexed your field using {@link
+    ///  FloatDocValuesField} then pass <seealso cref="FloatFieldSource"/>
+    ///  as the <seealso cref="ValueSource"/>; if you used {@link
+    ///  DoubleDocValuesField} then pass {@link
+    ///  DoubleFieldSource} (this is the default used when you
+    ///  pass just a the field name).
+    /// 
+    ///  @lucene.experimental 
+    /// </para>
+    /// </summary>
+    public class DoubleRangeFacetCounts : RangeFacetCounts
+    {
+
+        /// <summary>
+        /// Create {@code RangeFacetCounts}, using {@link
+        ///  DoubleFieldSource} from the specified field. 
+        /// </summary>
+        public DoubleRangeFacetCounts(string field, FacetsCollector hits, params DoubleRange[] ranges)
+            : this(field, new DoubleFieldSource(field), hits, ranges)
+        {
+        }
+
+        /// <summary>
+        /// Create {@code RangeFacetCounts}, using the provided
+        ///  <seealso cref="ValueSource"/>. 
+        /// </summary>
+        public DoubleRangeFacetCounts(string field, ValueSource valueSource, FacetsCollector hits, params DoubleRange[] ranges)
+            : this(field, valueSource, hits, null, ranges)
+        {
+        }
+
+        /// <summary>
+        /// Create {@code RangeFacetCounts}, using the provided
+        ///  <seealso cref="ValueSource"/>, and using the provided Filter as
+        ///  a fastmatch: only documents passing the filter are
+        ///  checked for the matching ranges.  The filter must be
+        ///  random access (implement <seealso cref="DocIdSet#bits"/>). 
+        /// </summary>
+        public DoubleRangeFacetCounts(string field, ValueSource valueSource, FacetsCollector hits, Filter fastMatchFilter, params DoubleRange[] ranges)
+            : base(field, ranges, fastMatchFilter)
+        {
+            Count(valueSource, hits.GetMatchingDocs);
+        }
+
+        private void Count(ValueSource valueSource, IEnumerable<MatchingDocs> matchingDocs)
+        {
+
+            DoubleRange[] ranges = (DoubleRange[])this.ranges;
+
+            LongRange[] longRanges = new LongRange[ranges.Length];
+            for (int i = 0; i < ranges.Length; i++)
+            {
+                DoubleRange range = ranges[i];
+                longRanges[i] = new LongRange(range.Label, NumericUtils.DoubleToSortableLong(range.minIncl), true, NumericUtils.DoubleToSortableLong(range.maxIncl), true);
+            }
+
+            LongRangeCounter counter = new LongRangeCounter(longRanges);
+
+            int missingCount = 0;
+            foreach (MatchingDocs hits in matchingDocs)
+            {
+                FunctionValues fv = valueSource.GetValues(new Dictionary<string,object>(), hits.context);
+
+                totCount += hits.totalHits;
+                Bits bits;
+                if (fastMatchFilter != null)
+                {
+                    DocIdSet dis = fastMatchFilter.GetDocIdSet(hits.context, null);
+                    if (dis == null)
+                    {
+                        // No documents match
+                        continue;
+                    }
+                    bits = dis.GetBits();
+                    if (bits == null)
+                    {
+                        throw new System.ArgumentException("fastMatchFilter does not implement DocIdSet.bits");
+                    }
+                }
+                else
+                {
+                    bits = null;
+                }
+
+                DocIdSetIterator docs = hits.bits.GetIterator();
+
+                int doc;
+                while ((doc = docs.NextDoc()) != DocIdSetIterator.NO_MORE_DOCS)
+                {
+                    if (bits != null && bits.Get(doc) == false)
+                    {
+                        doc++;
+                        continue;
+                    }
+                    // Skip missing docs:
+                    if (fv.Exists(doc))
+                    {
+                        counter.add(NumericUtils.DoubleToSortableLong(fv.DoubleVal(doc)));
+                    }
+                    else
+                    {
+                        missingCount++;
+                    }
+                }
+            }
+
+            missingCount += counter.fillCounts(counts);
+            totCount -= missingCount;
+        }
+    }
+
+}
\ No newline at end of file


[23/26] lucenenet git commit: fix double range facet count test

Posted by sy...@apache.org.
fix double range facet count test


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

Branch: refs/heads/master
Commit: f1262cd5a714bbff190af52028b93f1ce1c0d244
Parents: f9ee124
Author: eladmarg <el...@gmail.com>
Authored: Thu Nov 13 09:26:03 2014 -0800
Committer: eladmarg <el...@gmail.com>
Committed: Thu Nov 13 09:26:03 2014 -0800

----------------------------------------------------------------------
 Lucene.Net.Facet/Range/DoubleRangeFacetCounts.cs              | 2 +-
 src/Lucene.Net.Tests/core/Facet/Range/TestRangeFacetCounts.cs | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f1262cd5/Lucene.Net.Facet/Range/DoubleRangeFacetCounts.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Range/DoubleRangeFacetCounts.cs b/Lucene.Net.Facet/Range/DoubleRangeFacetCounts.cs
index 4bfea35..d0163d7 100644
--- a/Lucene.Net.Facet/Range/DoubleRangeFacetCounts.cs
+++ b/Lucene.Net.Facet/Range/DoubleRangeFacetCounts.cs
@@ -84,7 +84,7 @@ namespace Lucene.Net.Facet.Range
         ///  checked for the matching ranges.  The filter must be
         ///  random access (implement <seealso cref="DocIdSet#bits"/>). 
         /// </summary>
-        public DoubleRangeFacetCounts(string field, ValueSource valueSource, FacetsCollector hits, Filter fastMatchFilter, params DoubleRange[] ranges)
+        public DoubleRangeFacetCounts(string field, ValueSource valueSource, FacetsCollector hits, Filter fastMatchFilter, DoubleRange[] ranges)
             : base(field, ranges, fastMatchFilter)
         {
             Count(valueSource, hits.GetMatchingDocs);

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f1262cd5/src/Lucene.Net.Tests/core/Facet/Range/TestRangeFacetCounts.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Range/TestRangeFacetCounts.cs b/src/Lucene.Net.Tests/core/Facet/Range/TestRangeFacetCounts.cs
index aae9008..bb09c43 100644
--- a/src/Lucene.Net.Tests/core/Facet/Range/TestRangeFacetCounts.cs
+++ b/src/Lucene.Net.Tests/core/Facet/Range/TestRangeFacetCounts.cs
@@ -1152,9 +1152,9 @@ namespace Lucene.Net.Facet.Range
                 : base(indexSearcher, facetsConfig, org)
             {
                 this.outerInstance = outerInstance;
-                this.vs = vs;
-                this.ranges = ranges;
-                this.fastMatchFilter = fastMatchFilter;
+                this.vs = valueSource;
+                this.ranges = doubleRanges;
+                this.fastMatchFilter = filter;
             }
 
 


[22/26] lucenenet git commit: dot-netify (change to upper case functions and properties)

Posted by sy...@apache.org.
dot-netify (change to upper case functions and properties)


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

Branch: refs/heads/master
Commit: f9ee12403c9f293ab65245cba34ad31cdc81d0af
Parents: 70e1c54
Author: eladmarg <el...@gmail.com>
Authored: Thu Nov 13 00:56:30 2014 -0800
Committer: eladmarg <el...@gmail.com>
Committed: Thu Nov 13 00:56:30 2014 -0800

----------------------------------------------------------------------
 Lucene.Net.Facet/DrillDownQuery.cs              |   4 +-
 Lucene.Net.Facet/FacetField.cs                  |   8 +-
 Lucene.Net.Facet/FacetResult.cs                 | 171 ++++++++++---------
 Lucene.Net.Facet/FacetsConfig.cs                |  95 ++++++-----
 Lucene.Net.Facet/MultiFacets.cs                 |   6 +-
 .../RandomSamplingFacetsCollector.cs            |  26 +--
 Lucene.Net.Facet/Range/DoubleRange.cs           |  25 +--
 .../Range/DoubleRangeFacetCounts.cs             |  12 +-
 Lucene.Net.Facet/Range/LongRangeFacetCounts.cs  |  12 +-
 Lucene.Net.Facet/Range/RangeFacetCounts.cs      |  30 ++--
 .../SortedSet/SortedSetDocValuesFacetCounts.cs  |  20 +--
 .../SortedSet/SortedSetDocValuesFacetField.cs   |   4 +-
 .../Taxonomy/AssociationFacetField.cs           |   4 +-
 .../Directory/DirectoryTaxonomyReader.cs        |   8 +-
 .../Directory/DirectoryTaxonomyWriter.cs        |  39 +++--
 Lucene.Net.Facet/Taxonomy/FacetLabel.cs         |  71 ++++----
 .../Taxonomy/FloatTaxonomyFacets.cs             |  12 +-
 Lucene.Net.Facet/Taxonomy/IntTaxonomyFacets.cs  |  20 +--
 Lucene.Net.Facet/Taxonomy/PrintTaxonomyStats.cs |   4 +-
 Lucene.Net.Facet/Taxonomy/TaxonomyFacets.cs     |  22 +--
 Lucene.Net.Facet/Taxonomy/TaxonomyReader.cs     |   4 +-
 .../Taxonomy/WriterCache/CategoryPathUtils.cs   |  20 +--
 .../Taxonomy/WriterCache/CharBlockArray.cs      |   6 +-
 .../Taxonomy/WriterCache/CollisionMap.cs        |  41 ++++-
 .../WriterCache/CompactLabelToOrdinal.cs        |  13 +-
 Lucene.Net.Facet/TopOrdAndIntQueue.cs           |  10 +-
 .../Index/SortedSetDocValuesWriter.cs           |   2 +-
 .../core/Facet/FacetTestCase.cs                 |  34 ++--
 .../core/Facet/Range/TestRangeFacetCounts.cs    |  36 ++--
 .../Facet/Taxonomy/Directory/TestAddTaxonomy.cs |  10 +-
 .../Directory/TestConcurrentFacetedIndexing.cs  |   6 +-
 .../Directory/TestDirectoryTaxonomyReader.cs    |   8 +-
 .../Directory/TestDirectoryTaxonomyWriter.cs    |  10 +-
 .../core/Facet/Taxonomy/TestFacetLabel.cs       |  22 +--
 .../Taxonomy/TestSearcherTaxonomyManager.cs     |   8 +-
 .../core/Facet/Taxonomy/TestTaxonomyCombined.cs |  11 +-
 .../Facet/Taxonomy/TestTaxonomyFacetCounts.cs   |  26 +--
 .../Facet/Taxonomy/TestTaxonomyFacetCounts2.cs  |  32 ++--
 .../Taxonomy/TestTaxonomyFacetSumValueSource.cs |   4 +-
 .../core/Facet/TestDrillDownQuery.cs            |   4 +-
 .../core/Facet/TestDrillSideways.cs             |  12 +-
 .../core/Facet/TestFacetsConfig.cs              |   8 +-
 .../core/Facet/TestMultipleIndexFields.cs       |  14 +-
 .../Facet/TestRandomSamplingFacetsCollector.cs  |   8 +-
 44 files changed, 491 insertions(+), 451 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/DrillDownQuery.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/DrillDownQuery.cs b/Lucene.Net.Facet/DrillDownQuery.cs
index f49bb8f..8234282 100644
--- a/Lucene.Net.Facet/DrillDownQuery.cs
+++ b/Lucene.Net.Facet/DrillDownQuery.cs
@@ -161,7 +161,7 @@ namespace Lucene.Net.Facet
                 // merge a facet label in:
                 throw new Exception("cannot merge with custom Query");
             }
-            string indexedField = config.GetDimConfig(dim).indexFieldName;
+            string indexedField = config.GetDimConfig(dim).IndexFieldName;
 
             BooleanQuery bq = (BooleanQuery)q.Query;
             bq.Add(new TermQuery(Term(indexedField, dim, path)), Occur.SHOULD);
@@ -181,7 +181,7 @@ namespace Lucene.Net.Facet
                 Merge(dim, path);
                 return;
             }
-            string indexedField = config.GetDimConfig(dim).indexFieldName;
+            string indexedField = config.GetDimConfig(dim).IndexFieldName;
 
             BooleanQuery bq = new BooleanQuery(true); // disable coord
             bq.Add(new TermQuery(Term(indexedField, dim, path)), Occur.SHOULD);

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/FacetField.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/FacetField.cs b/Lucene.Net.Facet/FacetField.cs
index 7d5c1c5..a11e5cf 100644
--- a/Lucene.Net.Facet/FacetField.cs
+++ b/Lucene.Net.Facet/FacetField.cs
@@ -56,10 +56,10 @@ namespace Lucene.Net.Facet
         public FacetField(string dim, params string[] path)
             : base("dummy", TYPE)
         {
-            verifyLabel(dim);
+            VerifyLabel(dim);
             foreach (string label in path)
             {
-                verifyLabel(label);
+                VerifyLabel(label);
             }
             this.dim = dim;
             if (path.Length == 0)
@@ -71,7 +71,7 @@ namespace Lucene.Net.Facet
 
         public override string ToString()
         {
-            return "FacetField(dim=" + dim + " path=" + Arrays.ToString(path) + ")";
+            return "FacetField(dim=" + dim + " path=[" + Arrays.ToString(path) + "])";
         }
 
         /// <summary>
@@ -79,7 +79,7 @@ namespace Lucene.Net.Facet
         /// 
         ///  @lucene.internal 
         /// </summary>
-        public static void verifyLabel(string label)
+        public static void VerifyLabel(string label)
         {
             if (string.IsNullOrEmpty(label))
             {

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/FacetResult.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/FacetResult.cs b/Lucene.Net.Facet/FacetResult.cs
index f53c841..be619b2 100644
--- a/Lucene.Net.Facet/FacetResult.cs
+++ b/Lucene.Net.Facet/FacetResult.cs
@@ -1,103 +1,104 @@
 using System.Text;
 using Lucene.Net.Support;
+using Lucene.Net.Util;
 
 namespace Lucene.Net.Facet
 {
 
-	/*
-	 * 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.
-	 */
+    /*
+     * 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.
+     */
 
-	/// <summary>
-	/// Counts or aggregates for a single dimension. </summary>
-	public sealed class FacetResult
-	{
+    /// <summary>
+    /// Counts or aggregates for a single dimension. </summary>
+    public sealed class FacetResult
+    {
 
-	  /// <summary>
-	  /// Dimension that was requested. </summary>
-	  public readonly string dim;
+        /// <summary>
+        /// Dimension that was requested. </summary>
+        public readonly string Dim;
 
-	  /// <summary>
-	  /// Path whose children were requested. </summary>
-	  public readonly string[] path;
+        /// <summary>
+        /// Path whose children were requested. </summary>
+        public readonly string[] Path;
 
-	  /// <summary>
-	  /// Total value for this path (sum of all child counts, or
-	  ///  sum of all child values), even those not included in
-	  ///  the topN. 
-	  /// </summary>
-	  public readonly float value;
+        /// <summary>
+        /// Total value for this path (sum of all child counts, or
+        ///  sum of all child values), even those not included in
+        ///  the topN. 
+        /// </summary>
+        public readonly float Value;
 
-	  /// <summary>
-	  /// How many child labels were encountered. </summary>
-	  public readonly int childCount;
+        /// <summary>
+        /// How many child labels were encountered. </summary>
+        public readonly int ChildCount;
 
-	  /// <summary>
-	  /// Child counts. </summary>
-	  public readonly LabelAndValue[] labelValues;
+        /// <summary>
+        /// Child counts. </summary>
+        public readonly LabelAndValue[] LabelValues;
 
-	  /// <summary>
-	  /// Sole constructor. </summary>
-	  public FacetResult(string dim, string[] path, float value, LabelAndValue[] labelValues, int childCount)
-	  {
-		this.dim = dim;
-		this.path = path;
-		this.value = value;
-		this.labelValues = labelValues;
-		this.childCount = childCount;
-	  }
+        /// <summary>
+        /// Sole constructor. </summary>
+        public FacetResult(string dim, string[] path, float value, LabelAndValue[] labelValues, int childCount)
+        {
+            this.Dim = dim;
+            this.Path = path;
+            this.Value = value;
+            this.LabelValues = labelValues;
+            this.ChildCount = childCount;
+        }
 
-	  public override string ToString()
-	  {
-		StringBuilder sb = new StringBuilder();
-		sb.Append("dim=");
-		sb.Append(dim);
-		sb.Append(" path=");
-		sb.Append("[" + Arrays.ToString(path) + "]");
-		sb.Append(" value=");
-		sb.Append(value);
-		sb.Append(" childCount=");
-		sb.Append(childCount);
-		sb.Append('\n');
-		foreach (LabelAndValue labelValue in labelValues)
-		{
-		  sb.Append("  " + labelValue + "\n");
-		}
-		return sb.ToString();
-	  }
+        public override string ToString()
+        {
+            StringBuilder sb = new StringBuilder();
+            sb.Append("dim=");
+            sb.Append(Dim);
+            sb.Append(" path=");
+            sb.Append("[" + Arrays.ToString(Path) + "]");
+            sb.Append(" value=");
+            sb.Append(Value);
+            sb.Append(" childCount=");
+            sb.Append(ChildCount);
+            sb.Append('\n');
+            foreach (LabelAndValue labelValue in LabelValues)
+            {
+                sb.Append("  " + labelValue + "\n");
+            }
+            return sb.ToString();
+        }
 
-	  public override bool Equals(object _other)
-	  {
-		if ((_other is FacetResult) == false)
-		{
-		  return false;
-		}
-		FacetResult other = (FacetResult) _other;
-		return value.Equals(other.value) && childCount == other.childCount && Arrays.Equals(labelValues, other.labelValues);
-	  }
+        public override bool Equals(object _other)
+        {
+            if ((_other is FacetResult) == false)
+            {
+                return false;
+            }
+            FacetResult other = (FacetResult)_other;
+            return Value.Equals(other.Value) && ChildCount == other.ChildCount && Arrays.Equals(LabelValues, other.LabelValues);
+        }
 
-	  public override int GetHashCode()
-	  {
-		int hashCode = value.GetHashCode() + 31 * childCount;
-		foreach (LabelAndValue labelValue in labelValues)
-		{
-		  hashCode = labelValue.GetHashCode() + 31 * hashCode;
-		}
-		return hashCode;
-	  }
-	}
+        public override int GetHashCode()
+        {
+            int hashCode = Value.GetHashCode() + 31 * ChildCount;
+            foreach (LabelAndValue labelValue in LabelValues)
+            {
+                hashCode = labelValue.GetHashCode() + 31 * hashCode;
+            }
+            return hashCode;
+        }
+    }
 
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/FacetsConfig.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/FacetsConfig.cs b/Lucene.Net.Facet/FacetsConfig.cs
index ae4ac36..2539c8b 100644
--- a/Lucene.Net.Facet/FacetsConfig.cs
+++ b/Lucene.Net.Facet/FacetsConfig.cs
@@ -85,23 +85,23 @@ namespace Lucene.Net.Facet
         {
             /// <summary>
             /// True if this dimension is hierarchical. </summary>
-            public bool hierarchical;
+            public bool Hierarchical;
 
             /// <summary>
             /// True if this dimension is multi-valued. </summary>
-            public bool multiValued;
+            public bool MultiValued;
 
             /// <summary>
             /// True if the count/aggregate for the entire dimension
             ///  is required, which is unusual (default is false). 
             /// </summary>
-            public bool requireDimCount;
+            public bool RequireDimCount;
 
             /// <summary>
             /// Actual field where this dimension's facet labels
             ///  should be indexed 
             /// </summary>
-            public string indexFieldName = DEFAULT_INDEX_FIELD_NAME;
+            public string IndexFieldName = DEFAULT_INDEX_FIELD_NAME;
 
             /// <summary>
             /// Default constructor. </summary>
@@ -143,7 +143,7 @@ namespace Lucene.Net.Facet
             lock (this)
             {
                 DimConfig ft;
-                if (!fieldTypes.TryGetValue(dimName,out ft))
+                if (!fieldTypes.TryGetValue(dimName, out ft))
                 {
                     ft = DefaultDimConfig;
                 }
@@ -159,13 +159,15 @@ namespace Lucene.Net.Facet
         {
             lock (this)
             {
-                DimConfig ft;
-                if (!fieldTypes.TryGetValue(dimName,out ft))
+                if (!fieldTypes.ContainsKey(dimName))
                 {
-                    ft = new DimConfig();
+                    var ft = new DimConfig { Hierarchical = v };
                     fieldTypes[dimName] = ft;
                 }
-                ft.hierarchical = v;
+                else
+                {
+                    fieldTypes[dimName].Hierarchical = v;
+                }
             }
         }
 
@@ -177,13 +179,15 @@ namespace Lucene.Net.Facet
         {
             lock (this)
             {
-                DimConfig ft;
-                if (!fieldTypes.TryGetValue(dimName,out ft))
+                if (!fieldTypes.ContainsKey(dimName))
                 {
-                    ft = new DimConfig();
+                    var ft = new DimConfig { MultiValued = v };
                     fieldTypes[dimName] = ft;
                 }
-                ft.multiValued = v;
+                else
+                {
+                    fieldTypes[dimName].MultiValued = v;
+                }
             }
         }
 
@@ -196,13 +200,15 @@ namespace Lucene.Net.Facet
         {
             lock (this)
             {
-                DimConfig ft;
-                if (!fieldTypes.TryGetValue(dimName,out ft))
+                if (!fieldTypes.ContainsKey(dimName))
                 {
-                    ft = new DimConfig();
+                    var ft = new DimConfig { RequireDimCount = v };
                     fieldTypes[dimName] = ft;
                 }
-                ft.requireDimCount = v;
+                else
+                {
+                    fieldTypes[dimName].RequireDimCount = v;
+                }
             }
         }
 
@@ -215,13 +221,15 @@ namespace Lucene.Net.Facet
         {
             lock (this)
             {
-                DimConfig ft;
-                if (!fieldTypes.TryGetValue(dimName,out ft))
+                if (!fieldTypes.ContainsKey(dimName))
                 {
-                    ft = new DimConfig();
+                    var ft = new DimConfig { IndexFieldName = indexFieldName };
                     fieldTypes[dimName] = ft;
                 }
-                ft.indexFieldName = indexFieldName;
+                else
+                {
+                    fieldTypes[dimName].IndexFieldName = indexFieldName;
+                }
             }
         }
 
@@ -286,13 +294,13 @@ namespace Lucene.Net.Facet
                 {
                     FacetField facetField = (FacetField)field;
                     FacetsConfig.DimConfig dimConfig = GetDimConfig(facetField.dim);
-                    if (dimConfig.multiValued == false)
+                    if (dimConfig.MultiValued == false)
                     {
                         CheckSeen(seenDims, facetField.dim);
                     }
-                    string indexFieldName = dimConfig.indexFieldName;
-                    IList<FacetField> fields; 
-                    if (!byField.TryGetValue(indexFieldName,out fields))
+                    string indexFieldName = dimConfig.IndexFieldName;
+                    IList<FacetField> fields;
+                    if (!byField.TryGetValue(indexFieldName, out fields))
                     {
                         fields = new List<FacetField>();
                         byField[indexFieldName] = fields;
@@ -304,13 +312,13 @@ namespace Lucene.Net.Facet
                 {
                     var facetField = (SortedSetDocValuesFacetField)field;
                     FacetsConfig.DimConfig dimConfig = GetDimConfig(facetField.Dim);
-                    if (dimConfig.multiValued == false)
+                    if (dimConfig.MultiValued == false)
                     {
                         CheckSeen(seenDims, facetField.Dim);
                     }
-                    string indexFieldName = dimConfig.indexFieldName;
+                    string indexFieldName = dimConfig.IndexFieldName;
                     IList<SortedSetDocValuesFacetField> fields;
-                    if (!dvByField.TryGetValue(indexFieldName,out fields))
+                    if (!dvByField.TryGetValue(indexFieldName, out fields))
                     {
                         fields = new List<SortedSetDocValuesFacetField>();
                         dvByField[indexFieldName] = fields;
@@ -322,22 +330,22 @@ namespace Lucene.Net.Facet
                 {
                     AssociationFacetField facetField = (AssociationFacetField)field;
                     FacetsConfig.DimConfig dimConfig = GetDimConfig(facetField.dim);
-                    if (dimConfig.multiValued == false)
+                    if (dimConfig.MultiValued == false)
                     {
                         CheckSeen(seenDims, facetField.dim);
                     }
-                    if (dimConfig.hierarchical)
+                    if (dimConfig.Hierarchical)
                     {
                         throw new System.ArgumentException("AssociationFacetField cannot be hierarchical (dim=\"" + facetField.dim + "\")");
                     }
-                    if (dimConfig.requireDimCount)
+                    if (dimConfig.RequireDimCount)
                     {
                         throw new System.ArgumentException("AssociationFacetField cannot requireDimCount (dim=\"" + facetField.dim + "\")");
                     }
 
-                    string indexFieldName = dimConfig.indexFieldName;
+                    string indexFieldName = dimConfig.IndexFieldName;
                     IList<AssociationFacetField> fields;
-                    if (!assocByField.TryGetValue(indexFieldName,out fields))
+                    if (!assocByField.TryGetValue(indexFieldName, out fields))
                     {
                         fields = new List<AssociationFacetField>();
                         assocByField[indexFieldName] = fields;
@@ -361,7 +369,7 @@ namespace Lucene.Net.Facet
                     }
                     // NOTE: not thread safe, but this is just best effort:
                     string curType;
-                    if (!assocDimTypes.TryGetValue(indexFieldName,out curType))
+                    if (!assocDimTypes.TryGetValue(indexFieldName, out curType))
                     {
                         assocDimTypes[indexFieldName] = type;
                     }
@@ -406,7 +414,7 @@ namespace Lucene.Net.Facet
                 {
 
                     FacetsConfig.DimConfig ft = GetDimConfig(facetField.dim);
-                    if (facetField.path.Length > 1 && ft.hierarchical == false)
+                    if (facetField.path.Length > 1 && ft.Hierarchical == false)
                     {
                         throw new System.ArgumentException("dimension \"" + facetField.dim + "\" is not hierarchical yet has " + facetField.path.Length + " components");
                     }
@@ -423,7 +431,7 @@ namespace Lucene.Net.Facet
                     //System.out.println("ords[" + (ordinals.length-1) + "]=" + ordinal);
                     //System.out.println("  add cp=" + cp);
 
-                    if (ft.multiValued && (ft.hierarchical || ft.requireDimCount))
+                    if (ft.MultiValued && (ft.Hierarchical || ft.RequireDimCount))
                     {
                         //System.out.println("  add parents");
                         // Add all parents too:
@@ -438,7 +446,7 @@ namespace Lucene.Net.Facet
                             parent = taxoWriter.GetParent(parent);
                         }
 
-                        if (ft.requireDimCount == false)
+                        if (ft.RequireDimCount == false)
                         {
                             // Remove last (dimension) ord:
                             ordinals.Length--;
@@ -446,9 +454,9 @@ namespace Lucene.Net.Facet
                     }
 
                     // Drill down:
-                    for (int i = 1; i <= cp.length; i++)
+                    for (int i = 1; i <= cp.Length; i++)
                     {
-                        doc.Add(new StringField(indexFieldName, PathToString(cp.components, i), Field.Store.NO));
+                        doc.Add(new StringField(indexFieldName, PathToString(cp.Components, i), Field.Store.NO));
                     }
                 }
 
@@ -470,7 +478,7 @@ namespace Lucene.Net.Facet
                 foreach (SortedSetDocValuesFacetField facetField in ent.Value)
                 {
                     FacetLabel cp = new FacetLabel(facetField.Dim, facetField.Label);
-                    string fullPath = PathToString(cp.components, cp.length);
+                    string fullPath = PathToString(cp.Components, cp.Length);
                     //System.out.println("add " + fullPath);
 
                     // For facet counts:
@@ -513,9 +521,9 @@ namespace Lucene.Net.Facet
                     upto += field.assoc.Length;
 
                     // Drill down:
-                    for (int i = 1; i <= label.length; i++)
+                    for (int i = 1; i <= label.Length; i++)
                     {
-                        doc.Add(new StringField(indexFieldName, PathToString(label.components, i), Field.Store.NO));
+                        doc.Add(new StringField(indexFieldName, PathToString(label.Components, i), Field.Store.NO));
                     }
                 }
                 doc.Add(new BinaryDocValuesField(indexFieldName, new BytesRef(bytes, 0, upto)));
@@ -526,7 +534,7 @@ namespace Lucene.Net.Facet
         /// Encodes ordinals into a BytesRef; expert: subclass can
         ///  override this to change encoding. 
         /// </summary>
-        protected internal virtual BytesRef DedupAndEncode(IntsRef ordinals)
+        protected virtual BytesRef DedupAndEncode(IntsRef ordinals)
         {
             Array.Sort(ordinals.Ints, ordinals.Offset, ordinals.Length);
             byte[] bytes = new byte[5 * ordinals.Length];
@@ -585,6 +593,7 @@ namespace Lucene.Net.Facet
                     lastOrd = ord;
                 }
             }
+
             return new BytesRef(bytes, 0, upto);
         }
 

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/MultiFacets.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/MultiFacets.cs b/Lucene.Net.Facet/MultiFacets.cs
index b980a5d..b40772e 100644
--- a/Lucene.Net.Facet/MultiFacets.cs
+++ b/Lucene.Net.Facet/MultiFacets.cs
@@ -44,8 +44,8 @@ namespace Lucene.Net.Facet
 
         public override FacetResult GetTopChildren(int topN, string dim, params string[] path)
         {
-            Facets facets = dimToFacets[dim];
-            if (facets == null)
+            Facets facets;
+            if (!dimToFacets.TryGetValue(dim,out facets))
             {
                 if (defaultFacets == null)
                 {
@@ -89,7 +89,7 @@ namespace Lucene.Net.Facet
                 // already add that dim:
                 foreach (FacetResult result in defaultFacets.GetAllDims(topN))
                 {
-                    if (dimToFacets.ContainsKey(result.dim) == false)
+                    if (dimToFacets.ContainsKey(result.Dim) == false)
                     {
                         results.Add(result);
                     }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/RandomSamplingFacetsCollector.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/RandomSamplingFacetsCollector.cs b/Lucene.Net.Facet/RandomSamplingFacetsCollector.cs
index 02a4d9e..47bb029 100644
--- a/Lucene.Net.Facet/RandomSamplingFacetsCollector.cs
+++ b/Lucene.Net.Facet/RandomSamplingFacetsCollector.cs
@@ -278,34 +278,34 @@ namespace Lucene.Net.Facet
                 return res;
             }
 
-            LabelAndValue[] fixedLabelValues = new LabelAndValue[res.labelValues.Length];
+            LabelAndValue[] fixedLabelValues = new LabelAndValue[res.LabelValues.Length];
             IndexReader reader = searcher.IndexReader;
-            DimConfig dimConfig = config.GetDimConfig(res.dim);
+            DimConfig dimConfig = config.GetDimConfig(res.Dim);
 
             // +2 to prepend dimension, append child label
-            string[] childPath = new string[res.path.Length + 2];
-            childPath[0] = res.dim;
+            string[] childPath = new string[res.Path.Length + 2];
+            childPath[0] = res.Dim;
 
-            Array.Copy(res.path, 0, childPath, 1, res.path.Length); // reuse
+            Array.Copy(res.Path, 0, childPath, 1, res.Path.Length); // reuse
 
-            for (int i = 0; i < res.labelValues.Length; i++)
+            for (int i = 0; i < res.LabelValues.Length; i++)
             {
-                childPath[res.path.Length + 1] = res.labelValues[i].label;
+                childPath[res.Path.Length + 1] = res.LabelValues[i].label;
                 string fullPath = FacetsConfig.PathToString(childPath, childPath.Length);
-                int max = reader.DocFreq(new Term(dimConfig.indexFieldName, fullPath));
-                int correctedCount = (int)((double)res.labelValues[i].value / samplingRate);
+                int max = reader.DocFreq(new Term(dimConfig.IndexFieldName, fullPath));
+                int correctedCount = (int)((double)res.LabelValues[i].value / samplingRate);
                 correctedCount = Math.Min(max, correctedCount);
-                fixedLabelValues[i] = new LabelAndValue(res.labelValues[i].label, correctedCount);
+                fixedLabelValues[i] = new LabelAndValue(res.LabelValues[i].label, correctedCount);
             }
 
             // cap the total count on the total number of non-deleted documents in the reader
-            int correctedTotalCount = (int)res.value;
+            int correctedTotalCount = (int)res.Value;
             if (correctedTotalCount > 0)
             {
-                correctedTotalCount = Math.Min(reader.NumDocs, (int)((double)res.value / samplingRate));
+                correctedTotalCount = Math.Min(reader.NumDocs, (int)((double)res.Value / samplingRate));
             }
 
-            return new FacetResult(res.dim, res.path, correctedTotalCount, fixedLabelValues, res.childCount);
+            return new FacetResult(res.Dim, res.Path, correctedTotalCount, fixedLabelValues, res.ChildCount);
         }
 
         /// <summary>

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/Range/DoubleRange.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Range/DoubleRange.cs b/Lucene.Net.Facet/Range/DoubleRange.cs
index 6e7e5ae..8ad1439 100644
--- a/Lucene.Net.Facet/Range/DoubleRange.cs
+++ b/Lucene.Net.Facet/Range/DoubleRange.cs
@@ -44,52 +44,53 @@ namespace Lucene.Net.Facet.Range
 
         /// <summary>
         /// Minimum. </summary>
-        public readonly double min;
+        public readonly double Min;
 
         /// <summary>
         /// Maximum. </summary>
-        public readonly double max;
+        public readonly double Max;
 
         /// <summary>
         /// True if the minimum value is inclusive. </summary>
-        public readonly bool minInclusive;
+        public readonly bool MinInclusive;
 
         /// <summary>
         /// True if the maximum value is inclusive. </summary>
-        public readonly bool maxInclusive;
+        public readonly bool MaxInclusive;
 
+        private const double EPSILON = 1E-14;
         /// <summary>
         /// Create a DoubleRange. </summary>
         public DoubleRange(string label, double minIn, bool minInclusive, double maxIn, bool maxInclusive)
             : base(label)
         {
-            this.min = minIn;
-            this.max = maxIn;
-            this.minInclusive = minInclusive;
-            this.maxInclusive = maxInclusive;
+            this.Min = minIn;
+            this.Max = maxIn;
+            this.MinInclusive = minInclusive;
+            this.MaxInclusive = maxInclusive;
 
             // TODO: if DoubleDocValuesField used
             // NumericUtils.doubleToSortableLong format (instead of
             // Double.doubleToRawLongBits) we could do comparisons
             // in long space 
 
-            if (double.IsNaN(min))
+            if (double.IsNaN(Min))
             {
                 throw new System.ArgumentException("min cannot be NaN");
             }
             if (!minInclusive)
             {
-                minIn += double.Epsilon;
+                minIn += EPSILON;
             }
 
-            if (double.IsNaN(max))
+            if (double.IsNaN(Max))
             {
                 throw new System.ArgumentException("max cannot be NaN");
             }
             if (!maxInclusive)
             {
                 // Why no Math.nextDown?
-                maxIn = maxIn -= double.Epsilon;
+                maxIn = maxIn -= EPSILON;
             }
 
             if (minIn > maxIn)

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/Range/DoubleRangeFacetCounts.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Range/DoubleRangeFacetCounts.cs b/Lucene.Net.Facet/Range/DoubleRangeFacetCounts.cs
index f6908dc..4bfea35 100644
--- a/Lucene.Net.Facet/Range/DoubleRangeFacetCounts.cs
+++ b/Lucene.Net.Facet/Range/DoubleRangeFacetCounts.cs
@@ -93,7 +93,7 @@ namespace Lucene.Net.Facet.Range
         private void Count(ValueSource valueSource, IEnumerable<MatchingDocs> matchingDocs)
         {
 
-            DoubleRange[] ranges = (DoubleRange[])this.ranges;
+            DoubleRange[] ranges = (DoubleRange[])this.Ranges;
 
             LongRange[] longRanges = new LongRange[ranges.Length];
             for (int i = 0; i < ranges.Length; i++)
@@ -109,11 +109,11 @@ namespace Lucene.Net.Facet.Range
             {
                 FunctionValues fv = valueSource.GetValues(new Dictionary<string,object>(), hits.context);
 
-                totCount += hits.totalHits;
+                TotCount += hits.totalHits;
                 Bits bits;
-                if (fastMatchFilter != null)
+                if (FastMatchFilter != null)
                 {
-                    DocIdSet dis = fastMatchFilter.GetDocIdSet(hits.context, null);
+                    DocIdSet dis = FastMatchFilter.GetDocIdSet(hits.context, null);
                     if (dis == null)
                     {
                         // No documents match
@@ -152,8 +152,8 @@ namespace Lucene.Net.Facet.Range
                 }
             }
 
-            missingCount += counter.fillCounts(counts);
-            totCount -= missingCount;
+            missingCount += counter.fillCounts(Counts);
+            TotCount -= missingCount;
         }
     }
 

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/Range/LongRangeFacetCounts.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Range/LongRangeFacetCounts.cs b/Lucene.Net.Facet/Range/LongRangeFacetCounts.cs
index d8dfb15..8435bbe 100644
--- a/Lucene.Net.Facet/Range/LongRangeFacetCounts.cs
+++ b/Lucene.Net.Facet/Range/LongRangeFacetCounts.cs
@@ -80,7 +80,7 @@ namespace Lucene.Net.Facet.Range
         private void Count(ValueSource valueSource, IList<MatchingDocs> matchingDocs)
         {
 
-            LongRange[] ranges = (LongRange[])this.ranges;
+            LongRange[] ranges = (LongRange[])this.Ranges;
 
             LongRangeCounter counter = new LongRangeCounter(ranges);
 
@@ -89,11 +89,11 @@ namespace Lucene.Net.Facet.Range
             {
                 FunctionValues fv = valueSource.GetValues(new Dictionary<string, object>(), hits.context);
 
-                totCount += hits.totalHits;
+                TotCount += hits.totalHits;
                 Bits bits;
-                if (fastMatchFilter != null)
+                if (FastMatchFilter != null)
                 {
-                    DocIdSet dis = fastMatchFilter.GetDocIdSet(hits.context, null);
+                    DocIdSet dis = FastMatchFilter.GetDocIdSet(hits.context, null);
                     if (dis == null)
                     {
                         // No documents match
@@ -131,12 +131,12 @@ namespace Lucene.Net.Facet.Range
                 }
             }
 
-            int x = counter.fillCounts(counts);
+            int x = counter.fillCounts(Counts);
 
             missingCount += x;
 
             //System.out.println("totCount " + totCount + " missingCount " + counter.missingCount);
-            totCount -= missingCount;
+            TotCount -= missingCount;
         }
     }
 

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/Range/RangeFacetCounts.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Range/RangeFacetCounts.cs b/Lucene.Net.Facet/Range/RangeFacetCounts.cs
index 11c7e09..29e02fb 100644
--- a/Lucene.Net.Facet/Range/RangeFacetCounts.cs
+++ b/Lucene.Net.Facet/Range/RangeFacetCounts.cs
@@ -34,11 +34,11 @@ namespace Lucene.Net.Facet.Range
     {
         /// <summary>
         /// Ranges passed to constructor. </summary>
-        protected internal readonly Range[] ranges;
+        protected internal readonly Range[] Ranges;
 
         /// <summary>
         /// Counts, initialized in by subclass. </summary>
-        protected internal readonly int[] counts;
+        protected internal readonly int[] Counts;
 
         /// <summary>
         /// Optional: if specified, we first test this Filter to
@@ -46,42 +46,42 @@ namespace Lucene.Net.Facet.Range
         ///  matching ranges.  If this is null, all documents are
         ///  checked. 
         /// </summary>
-        protected internal readonly Filter fastMatchFilter;
+        protected internal readonly Filter FastMatchFilter;
 
         /// <summary>
         /// Our field name. </summary>
-        protected internal readonly string field;
+        protected internal readonly string Field;
 
         /// <summary>
         /// Total number of hits. </summary>
-        protected internal int totCount;
+        protected internal int TotCount;
 
         /// <summary>
         /// Create {@code RangeFacetCounts} </summary>
         protected internal RangeFacetCounts(string field, Range[] ranges, Filter fastMatchFilter)
         {
-            this.field = field;
-            this.ranges = ranges;
-            this.fastMatchFilter = fastMatchFilter;
-            counts = new int[ranges.Length];
+            this.Field = field;
+            this.Ranges = ranges;
+            this.FastMatchFilter = fastMatchFilter;
+            Counts = new int[ranges.Length];
         }
 
         public override FacetResult GetTopChildren(int topN, string dim, params string[] path)
         {
-            if (dim.Equals(field) == false)
+            if (dim.Equals(Field) == false)
             {
-                throw new System.ArgumentException("invalid dim \"" + dim + "\"; should be \"" + field + "\"");
+                throw new System.ArgumentException("invalid dim \"" + dim + "\"; should be \"" + Field + "\"");
             }
             if (path.Length != 0)
             {
                 throw new System.ArgumentException("path.length should be 0");
             }
-            LabelAndValue[] labelValues = new LabelAndValue[counts.Length];
-            for (int i = 0; i < counts.Length; i++)
+            LabelAndValue[] labelValues = new LabelAndValue[Counts.Length];
+            for (int i = 0; i < Counts.Length; i++)
             {
-                labelValues[i] = new LabelAndValue(ranges[i].Label, counts[i]);
+                labelValues[i] = new LabelAndValue(Ranges[i].Label, Counts[i]);
             }
-            return new FacetResult(dim, path, totCount, labelValues, labelValues.Length);
+            return new FacetResult(dim, path, TotCount, labelValues, labelValues.Length);
         }
 
         public override float GetSpecificValue(string dim, params string[] path)

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/SortedSet/SortedSetDocValuesFacetCounts.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/SortedSet/SortedSetDocValuesFacetCounts.cs b/Lucene.Net.Facet/SortedSet/SortedSetDocValuesFacetCounts.cs
index aa2f40a..8ccb190 100644
--- a/Lucene.Net.Facet/SortedSet/SortedSetDocValuesFacetCounts.cs
+++ b/Lucene.Net.Facet/SortedSet/SortedSetDocValuesFacetCounts.cs
@@ -123,8 +123,8 @@ namespace Lucene.Net.Facet.SortedSet
                         {
                             reuse = new TopOrdAndIntQueue.OrdAndValue();
                         }
-                        reuse.ord = ord;
-                        reuse.value = counts[ord];
+                        reuse.Ord = ord;
+                        reuse.Value = counts[ord];
                         if (q == null)
                         {
                             // Lazy init, so we don't create this for the
@@ -134,7 +134,7 @@ namespace Lucene.Net.Facet.SortedSet
                         reuse = q.InsertWithOverflow(reuse);
                         if (q.Size() == topN)
                         {
-                            bottomCount = q.Top().value;
+                            bottomCount = q.Top().Value;
                         }
                     }
                 }
@@ -150,9 +150,9 @@ namespace Lucene.Net.Facet.SortedSet
             {
                 TopOrdAndIntQueue.OrdAndValue ordAndValue = q.Pop();
                 var term = new BytesRef();
-                dv.LookupOrd(ordAndValue.ord, term);
+                dv.LookupOrd(ordAndValue.Ord, term);
                 string[] parts = FacetsConfig.StringToPath(term.Utf8ToString());
-                labelValues[i] = new LabelAndValue(parts[1], ordAndValue.value);
+                labelValues[i] = new LabelAndValue(parts[1], ordAndValue.Value);
             }
 
             return new FacetResult(dim, new string[0], dimCount, labelValues, childCount);
@@ -183,13 +183,13 @@ namespace Lucene.Net.Facet.SortedSet
             foreach (FacetsCollector.MatchingDocs hits in matchingDocs)
             {
 
-                AtomicReader reader = hits.context.AtomicReader;
+                var reader = hits.context.AtomicReader;
                 //System.out.println("  reader=" + reader);
                 // LUCENE-5090: make sure the provided reader context "matches"
                 // the top-level reader passed to the
                 // SortedSetDocValuesReaderState, else cryptic
                 // AIOOBE can happen:
-                if (ReaderUtil.GetTopLevelContext(hits.context).Reader != origReader)
+                if (!Equals(ReaderUtil.GetTopLevelContext(hits.context).Reader, origReader))
                 {
                     throw new ThreadStateException("the SortedSetDocValuesReaderState provided to this class does not match the reader being searched; you must create a new SortedSetDocValuesReaderState every time you open a new IndexReader");
                 }
@@ -331,17 +331,17 @@ namespace Lucene.Net.Facet.SortedSet
 
             public virtual int Compare(FacetResult a, FacetResult b)
             {
-                if ((int)a.value > (int)b.value)
+                if ((int)a.Value > (int)b.Value)
                 {
                     return -1;
                 }
-                else if ((int)b.value > (int)a.value)
+                else if ((int)b.Value > (int)a.Value)
                 {
                     return 1;
                 }
                 else
                 {
-                    return a.dim.CompareTo(b.dim);
+                    return a.Dim.CompareTo(b.Dim);
                 }
             }
         }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/SortedSet/SortedSetDocValuesFacetField.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/SortedSet/SortedSetDocValuesFacetField.cs b/Lucene.Net.Facet/SortedSet/SortedSetDocValuesFacetField.cs
index ee011a3..c860d56 100644
--- a/Lucene.Net.Facet/SortedSet/SortedSetDocValuesFacetField.cs
+++ b/Lucene.Net.Facet/SortedSet/SortedSetDocValuesFacetField.cs
@@ -50,8 +50,8 @@
         public SortedSetDocValuesFacetField(string dim, string label)
             : base("dummy", TYPE)
         {
-            FacetField.verifyLabel(label);
-            FacetField.verifyLabel(dim);
+            FacetField.VerifyLabel(label);
+            FacetField.VerifyLabel(dim);
             this.Dim = dim;
             this.Label = label;
         }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/Taxonomy/AssociationFacetField.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/AssociationFacetField.cs b/Lucene.Net.Facet/Taxonomy/AssociationFacetField.cs
index 5835fd2..6113eef 100644
--- a/Lucene.Net.Facet/Taxonomy/AssociationFacetField.cs
+++ b/Lucene.Net.Facet/Taxonomy/AssociationFacetField.cs
@@ -68,10 +68,10 @@ namespace Lucene.Net.Facet.Taxonomy
         public AssociationFacetField(BytesRef assoc, string dim, params string[] path)
             : base("dummy", TYPE)
         {
-            FacetField.verifyLabel(dim);
+            FacetField.VerifyLabel(dim);
             foreach (string label in path)
             {
-                FacetField.verifyLabel(label);
+                FacetField.VerifyLabel(label);
             }
             this.dim = dim;
             this.assoc = assoc;

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/Taxonomy/Directory/DirectoryTaxonomyReader.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/Directory/DirectoryTaxonomyReader.cs b/Lucene.Net.Facet/Taxonomy/Directory/DirectoryTaxonomyReader.cs
index f61187c..a567210 100644
--- a/Lucene.Net.Facet/Taxonomy/Directory/DirectoryTaxonomyReader.cs
+++ b/Lucene.Net.Facet/Taxonomy/Directory/DirectoryTaxonomyReader.cs
@@ -163,7 +163,7 @@ namespace Lucene.Net.Facet.Taxonomy.Directory
         /// through <seealso cref="#setCacheSize(int)"/>, it will affect both reader instances.
         /// </para>
         /// </summary>
-        protected internal override TaxonomyReader DoOpenIfChanged()
+        protected override TaxonomyReader DoOpenIfChanged()
         {
             EnsureOpen();
 
@@ -287,7 +287,7 @@ namespace Lucene.Net.Facet.Taxonomy.Directory
         public override int GetOrdinal(FacetLabel cp)
         {
             EnsureOpen();
-            if (cp.length == 0)
+            if (cp.Length == 0)
             {
                 return ROOT_ORDINAL;
             }
@@ -319,7 +319,7 @@ namespace Lucene.Net.Facet.Taxonomy.Directory
             // If we're still here, we have a cache miss. We need to fetch the
             // value from disk, and then also put it in the cache:
             int ret = TaxonomyReader.INVALID_ORDINAL;
-            DocsEnum docs = MultiFields.GetTermDocsEnum(indexReader, null, Consts.FULL, new BytesRef(FacetsConfig.PathToString(cp.components, cp.length)), 0);
+            DocsEnum docs = MultiFields.GetTermDocsEnum(indexReader, null, Consts.FULL, new BytesRef(FacetsConfig.PathToString(cp.Components, cp.Length)), 0);
             if (docs != null && docs.NextDoc() != DocIdSetIterator.NO_MORE_DOCS)
             {
                 ret = docs.DocID();
@@ -426,7 +426,7 @@ namespace Lucene.Net.Facet.Taxonomy.Directory
                         sb.Append(i + ": NULL!! \n");
                         continue;
                     }
-                    if (category.length == 0)
+                    if (category.Length == 0)
                     {
                         sb.Append(i + ": EMPTY STRING!! \n");
                         continue;

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/Taxonomy/Directory/DirectoryTaxonomyWriter.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/Directory/DirectoryTaxonomyWriter.cs b/Lucene.Net.Facet/Taxonomy/Directory/DirectoryTaxonomyWriter.cs
index 6802b57..63967ee 100644
--- a/Lucene.Net.Facet/Taxonomy/Directory/DirectoryTaxonomyWriter.cs
+++ b/Lucene.Net.Facet/Taxonomy/Directory/DirectoryTaxonomyWriter.cs
@@ -1,5 +1,6 @@
 using System;
 using System.Collections;
+using System.Collections.Concurrent;
 using System.Diagnostics;
 using System.Collections.Generic;
 using System.IO;
@@ -438,7 +439,7 @@ namespace Lucene.Net.Facet.Taxonomy.Directory
                 DirectoryReader reader = readerManager.Acquire();
                 try
                 {
-                    BytesRef catTerm = new BytesRef(FacetsConfig.PathToString(categoryPath.components, categoryPath.length));
+                    BytesRef catTerm = new BytesRef(FacetsConfig.PathToString(categoryPath.Components, categoryPath.Length));
                     TermsEnum termsEnum = null; // reuse
                     DocsEnum docs = null; // reuse
                     foreach (AtomicReaderContext ctx in reader.Leaves)
@@ -514,16 +515,16 @@ namespace Lucene.Net.Facet.Taxonomy.Directory
             // ordinal as payloads (rather than a stored field; payloads can be
             // more efficiently read into memory in bulk by LuceneTaxonomyReader)
             int parent;
-            if (cp.length > 1)
+            if (cp.Length > 1)
             {
-                FacetLabel parentPath = cp.Subpath(cp.length - 1);
+                FacetLabel parentPath = cp.Subpath(cp.Length - 1);
                 parent = FindCategory(parentPath);
                 if (parent < 0)
                 {
                     parent = InternalAddCategory(parentPath);
                 }
             }
-            else if (cp.length == 1)
+            else if (cp.Length == 1)
             {
                 parent = TaxonomyReader.ROOT_ORDINAL;
             }
@@ -567,7 +568,7 @@ namespace Lucene.Net.Facet.Taxonomy.Directory
             Document d = new Document();
             d.Add(parentStreamField);
 
-            fullPathField.StringValue = FacetsConfig.PathToString(categoryPath.components, categoryPath.length);
+            fullPathField.StringValue = FacetsConfig.PathToString(categoryPath.Components, categoryPath.Length);
             d.Add(fullPathField);
 
             // Note that we do no pass an Analyzer here because the fields that are
@@ -986,7 +987,7 @@ namespace Lucene.Net.Facet.Taxonomy.Directory
             /// needed. Calling it will also free all resources that the map might
             /// be holding (such as temporary disk space), other than the returned int[].
             /// </summary>
-            Dictionary<int,int> Map { get; }
+            int[] Map { get; }
         }
 
         /// <summary>
@@ -994,27 +995,37 @@ namespace Lucene.Net.Facet.Taxonomy.Directory
         /// </summary>
         public sealed class MemoryOrdinalMap : OrdinalMap
         {
-            internal Dictionary<int, int> map;
+            internal int[] map;
 
             /// <summary>
             /// Sole constructor. 
             /// </summary>
             public MemoryOrdinalMap()
             {
-                map = new Dictionary<int,int>();
+                map = new int[] { };
             }
 
             public int Size { set; private get; }
 
             public void AddMapping(int origOrdinal, int newOrdinal)
             {
-                map[origOrdinal] = newOrdinal;
+                if (map.Length - 1 >= origOrdinal)
+                {
+                    map[origOrdinal] = newOrdinal;
+                }
+                else
+                {
+                    Array.Resize(ref map, origOrdinal + 1);
+                    map[origOrdinal] = newOrdinal;
+                }
+
+
             }
 
             public void AddDone() // nothing to do
             {
             }
-            public Dictionary<int, int> Map
+            public int[] Map
             {
                 get
                 {
@@ -1064,9 +1075,9 @@ namespace Lucene.Net.Facet.Taxonomy.Directory
                 }
             }
 
-            Dictionary<int,int> map = null;
+            int[] map = null;
 
-            public Dictionary<int,int> Map
+            public int[] Map
             {
                 get
                 {
@@ -1078,11 +1089,11 @@ namespace Lucene.Net.Facet.Taxonomy.Directory
 
                     var ifs = new FileStream(tmpfile, FileMode.OpenOrCreate, FileAccess.Read);
                     var @in = new InputStreamDataInput(ifs);
-                    map = new Dictionary<int, int>(@in.ReadInt());
+                    map = new int[@in.ReadInt()];
                     // NOTE: The current code assumes here that the map is complete,
                     // i.e., every ordinal gets one and exactly one value. Otherwise,
                     // we may run into an EOF here, or vice versa, not read everything.
-                    for (int i = 0; i < map.Count; i++)
+                    for (int i = 0; i < map.Length; i++)
                     {
                         int origordinal = @in.ReadInt();
                         int newordinal = @in.ReadInt();

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/Taxonomy/FacetLabel.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/FacetLabel.cs b/Lucene.Net.Facet/Taxonomy/FacetLabel.cs
index 287a18c..a448d08 100644
--- a/Lucene.Net.Facet/Taxonomy/FacetLabel.cs
+++ b/Lucene.Net.Facet/Taxonomy/FacetLabel.cs
@@ -22,8 +22,8 @@ namespace Lucene.Net.Facet.Taxonomy
      * limitations under the License.
      */
 
-    using LruTaxonomyWriterCache = Lucene.Net.Facet.Taxonomy.WriterCache.LruTaxonomyWriterCache; 
-    using NameHashIntCacheLRU = Lucene.Net.Facet.Taxonomy.WriterCache.NameHashIntCacheLRU; 
+    using LruTaxonomyWriterCache = Lucene.Net.Facet.Taxonomy.WriterCache.LruTaxonomyWriterCache;
+    using NameHashIntCacheLRU = Lucene.Net.Facet.Taxonomy.WriterCache.NameHashIntCacheLRU;
 
     /// <summary>
     /// Holds a sequence of string components, specifying the hierarchical name of a
@@ -51,11 +51,11 @@ namespace Lucene.Net.Facet.Taxonomy
         /// <seealso cref="#subpath(int)"/>, therefore you should traverse the array up to
         /// <seealso cref="#length"/> for this path's components.
         /// </summary>
-        public readonly string[] components;
+        public readonly string[] Components;
 
         /// <summary>
         /// The number of components of this <seealso cref="FacetLabel"/>. </summary>
-        public readonly int length;
+        public readonly int Length;
 
         // Used by subpath
         private FacetLabel(FacetLabel copyFrom, int prefixLen)
@@ -63,17 +63,17 @@ namespace Lucene.Net.Facet.Taxonomy
             // while the code which calls this method is safe, at some point a test
             // tripped on AIOOBE in toString, but we failed to reproduce. adding the
             // assert as a safety check.
-            Debug.Assert(prefixLen >= 0 && prefixLen <= copyFrom.components.Length, "prefixLen cannot be negative nor larger than the given components' length: prefixLen=" + prefixLen + " components.length=" + copyFrom.components.Length);
-            this.components = copyFrom.components;
-            length = prefixLen;
+            Debug.Assert(prefixLen >= 0 && prefixLen <= copyFrom.Components.Length, "prefixLen cannot be negative nor larger than the given components' length: prefixLen=" + prefixLen + " components.length=" + copyFrom.Components.Length);
+            this.Components = copyFrom.Components;
+            Length = prefixLen;
         }
 
         /// <summary>
         /// Construct from the given path components. </summary>
         public FacetLabel(params string[] components)
         {
-            this.components = components;
-            length = components.Length;
+            this.Components = components;
+            Length = components.Length;
             CheckComponents();
         }
 
@@ -81,28 +81,28 @@ namespace Lucene.Net.Facet.Taxonomy
         /// Construct from the dimension plus the given path components. </summary>
         public FacetLabel(string dim, string[] path)
         {
-            components = new string[1 + path.Length];
-            components[0] = dim;
-            Array.Copy(path, 0, components, 1, path.Length);
-            length = components.Length;
+            Components = new string[1 + path.Length];
+            Components[0] = dim;
+            Array.Copy(path, 0, Components, 1, path.Length);
+            Length = Components.Length;
             CheckComponents();
         }
 
         private void CheckComponents()
         {
             long len = 0;
-            foreach (string comp in components)
+            foreach (string comp in Components)
             {
                 if (string.IsNullOrEmpty(comp))
                 {
-                    throw new System.ArgumentException("empty or null components not allowed: " + Arrays.ToString(components));
+                    throw new System.ArgumentException("empty or null components not allowed: " + Arrays.ToString(Components));
                 }
                 len += comp.Length;
             }
-            len += components.Length - 1; // add separators
+            len += Components.Length - 1; // add separators
             if (len > MAX_CATEGORY_PATH_LENGTH)
             {
-                throw new System.ArgumentException("category path exceeds maximum allowed path length: max=" + MAX_CATEGORY_PATH_LENGTH + " len=" + len + " path=" + Arrays.ToString(components).Substring(0, 30) + "...");
+                throw new System.ArgumentException("category path exceeds maximum allowed path length: max=" + MAX_CATEGORY_PATH_LENGTH + " len=" + len + " path=" + Arrays.ToString(Components).Substring(0, 30) + "...");
             }
         }
 
@@ -112,10 +112,10 @@ namespace Lucene.Net.Facet.Taxonomy
         /// </summary>
         public virtual int CompareTo(FacetLabel other)
         {
-            int len = length < other.length ? length : other.length;
+            int len = Length < other.Length ? Length : other.Length;
             for (int i = 0, j = 0; i < len; i++, j++)
             {
-                int cmp = components[i].CompareTo(other.components[j]);
+                int cmp = Components[i].CompareTo(other.Components[j]);
                 if (cmp < 0)
                 {
                     return -1; // this is 'before'
@@ -127,7 +127,7 @@ namespace Lucene.Net.Facet.Taxonomy
             }
 
             // one is a prefix of the other
-            return length - other.length;
+            return Length - other.Length;
         }
 
         public override bool Equals(object obj)
@@ -138,16 +138,16 @@ namespace Lucene.Net.Facet.Taxonomy
             }
 
             FacetLabel other = (FacetLabel)obj;
-            if (length != other.length)
+            if (Length != other.Length)
             {
                 return false; // not same length, cannot be equal
             }
 
             // CategoryPaths are more likely to differ at the last components, so start
             // from last-first
-            for (int i = length - 1; i >= 0; i--)
+            for (int i = Length - 1; i >= 0; i--)
             {
-                if (!components[i].Equals(other.components[i]))
+                if (!string.Equals(Components[i], other.Components[i]))
                 {
                     return false;
                 }
@@ -157,17 +157,12 @@ namespace Lucene.Net.Facet.Taxonomy
 
         public override int GetHashCode()
         {
-            if (length == 0)
+            if (Length == 0)
             {
                 return 0;
             }
 
-            int hash = length;
-            for (int i = 0; i < length; i++)
-            {
-                hash = hash * 31 + components[i].GetHashCode();
-            }
-            return hash;
+            return Arrays.GetHashCode(Components);
         }
 
         /// <summary>
@@ -179,15 +174,15 @@ namespace Lucene.Net.Facet.Taxonomy
         /// </summary>
         public virtual long LongHashCode()
         {
-            if (length == 0)
+            if (Length == 0)
             {
                 return 0;
             }
 
-            long hash = length;
-            for (int i = 0; i < length; i++)
+            long hash = Length;
+            for (int i = 0; i < Length; i++)
             {
-                hash = hash * 65599 + components[i].GetHashCode();
+                hash = hash * 65599 + Components[i].GetHashCode();
             }
             return hash;
         }
@@ -196,7 +191,7 @@ namespace Lucene.Net.Facet.Taxonomy
         /// Returns a sub-path of this path up to {@code length} components. </summary>
         public virtual FacetLabel Subpath(int len)
         {
-            if (len >= this.length || len < 0)
+            if (len >= this.Length || len < 0)
             {
                 return this;
             }
@@ -211,12 +206,12 @@ namespace Lucene.Net.Facet.Taxonomy
         /// </summary>
         public override string ToString()
         {
-            if (length == 0)
+            if (Length == 0)
             {
                 return "FacetLabel: []";
             }
-            string[] parts = new string[length];
-            Array.Copy(components, 0, parts, 0, length);
+            string[] parts = new string[Length];
+            Array.Copy(Components, 0, parts, 0, Length);
             return "FacetLabel: [" + Arrays.ToString(parts) + "]";
         }
     }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/Taxonomy/FloatTaxonomyFacets.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/FloatTaxonomyFacets.cs b/Lucene.Net.Facet/Taxonomy/FloatTaxonomyFacets.cs
index 9dbe71d..3921540 100644
--- a/Lucene.Net.Facet/Taxonomy/FloatTaxonomyFacets.cs
+++ b/Lucene.Net.Facet/Taxonomy/FloatTaxonomyFacets.cs
@@ -52,7 +52,7 @@ namespace Lucene.Net.Facet.Taxonomy
             {
                 string dim = ent.Key;
                 FacetsConfig.DimConfig ft = ent.Value;
-                if (ft.hierarchical && ft.multiValued == false)
+                if (ft.Hierarchical && ft.MultiValued == false)
                 {
                     int dimRootOrd = TaxoReader.GetOrdinal(new FacetLabel(dim));
                     Debug.Assert(dimRootOrd > 0);
@@ -79,11 +79,11 @@ namespace Lucene.Net.Facet.Taxonomy
             FacetsConfig.DimConfig dimConfig = VerifyDim(dim);
             if (path.Length == 0)
             {
-                if (dimConfig.hierarchical && dimConfig.multiValued == false)
+                if (dimConfig.Hierarchical && dimConfig.MultiValued == false)
                 {
                     // ok: rolled up at search time
                 }
-                else if (dimConfig.requireDimCount && dimConfig.multiValued)
+                else if (dimConfig.RequireDimCount && dimConfig.MultiValued)
                 {
                     // ok: we indexed all ords at index time
                 }
@@ -152,9 +152,9 @@ namespace Lucene.Net.Facet.Taxonomy
                 return null;
             }
 
-            if (dimConfig.multiValued)
+            if (dimConfig.MultiValued)
             {
-                if (dimConfig.requireDimCount)
+                if (dimConfig.RequireDimCount)
                 {
                     sumValues = values[dimOrd];
                 }
@@ -174,7 +174,7 @@ namespace Lucene.Net.Facet.Taxonomy
             {
                 TopOrdAndFloatQueue.OrdAndValue ordAndValue = q.Pop();
                 FacetLabel child = TaxoReader.GetPath(ordAndValue.ord);
-                labelValues[i] = new LabelAndValue(child.components[cp.length], ordAndValue.value);
+                labelValues[i] = new LabelAndValue(child.Components[cp.Length], ordAndValue.value);
             }
 
             return new FacetResult(dim, path, sumValues, labelValues, childCount);

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/Taxonomy/IntTaxonomyFacets.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/IntTaxonomyFacets.cs b/Lucene.Net.Facet/Taxonomy/IntTaxonomyFacets.cs
index ad40137..f73f167 100644
--- a/Lucene.Net.Facet/Taxonomy/IntTaxonomyFacets.cs
+++ b/Lucene.Net.Facet/Taxonomy/IntTaxonomyFacets.cs
@@ -53,7 +53,7 @@ namespace Lucene.Net.Facet.Taxonomy
             {
                 string dim = ent.Key;
                 FacetsConfig.DimConfig ft = ent.Value;
-                if (ft.hierarchical && ft.multiValued == false)
+                if (ft.Hierarchical && ft.MultiValued == false)
                 {
                     int dimRootOrd = TaxoReader.GetOrdinal(new FacetLabel(dim));
                     // It can be -1 if this field was declared in the
@@ -84,11 +84,11 @@ namespace Lucene.Net.Facet.Taxonomy
             var dimConfig = VerifyDim(dim);
             if (path.Length == 0)
             {
-                if (dimConfig.hierarchical && dimConfig.multiValued == false)
+                if (dimConfig.Hierarchical && dimConfig.MultiValued == false)
                 {
                     // ok: rolled up at search time
                 }
-                else if (dimConfig.requireDimCount && dimConfig.multiValued)
+                else if (dimConfig.RequireDimCount && dimConfig.MultiValued)
                 {
                     // ok: we indexed all ords at index time
                 }
@@ -140,12 +140,12 @@ namespace Lucene.Net.Facet.Taxonomy
                         {
                             reuse = new TopOrdAndIntQueue.OrdAndValue();
                         }
-                        reuse.ord = ord;
-                        reuse.value = values[ord];
+                        reuse.Ord = ord;
+                        reuse.Value = values[ord];
                         reuse = q.InsertWithOverflow(reuse);
                         if (q.Size() == topN)
                         {
-                            bottomValue = q.Top().value;
+                            bottomValue = q.Top().Value;
                         }
                     }
                 }
@@ -158,9 +158,9 @@ namespace Lucene.Net.Facet.Taxonomy
                 return null;
             }
 
-            if (dimConfig.multiValued)
+            if (dimConfig.MultiValued)
             {
-                if (dimConfig.requireDimCount)
+                if (dimConfig.RequireDimCount)
                 {
                     totValue = values[dimOrd];
                 }
@@ -179,8 +179,8 @@ namespace Lucene.Net.Facet.Taxonomy
             for (int i = labelValues.Length - 1; i >= 0; i--)
             {
                 TopOrdAndIntQueue.OrdAndValue ordAndValue = q.Pop();
-                FacetLabel child = TaxoReader.GetPath(ordAndValue.ord);
-                labelValues[i] = new LabelAndValue(child.components[cp.length], ordAndValue.value);
+                FacetLabel child = TaxoReader.GetPath(ordAndValue.Ord);
+                labelValues[i] = new LabelAndValue(child.Components[cp.Length], ordAndValue.Value);
             }
 
             return new FacetResult(dim, path, totValue, labelValues, childCount);

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/Taxonomy/PrintTaxonomyStats.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/PrintTaxonomyStats.cs b/Lucene.Net.Facet/Taxonomy/PrintTaxonomyStats.cs
index bfcbda7..a4b44aa 100644
--- a/Lucene.Net.Facet/Taxonomy/PrintTaxonomyStats.cs
+++ b/Lucene.Net.Facet/Taxonomy/PrintTaxonomyStats.cs
@@ -86,7 +86,7 @@ namespace Lucene.Net.Facet.Taxonomy
                     numImmediateChildren++;
                 }
                 FacetLabel cp = r.GetPath(child);
-                @out.WriteLine("/" + cp.components[0] + ": " + numImmediateChildren + " immediate children; " + (1 + CountAllChildren(r, child)) + " total categories");
+                @out.WriteLine("/" + cp.Components[0] + ": " + numImmediateChildren + " immediate children; " + (1 + CountAllChildren(r, child)) + " total categories");
                 if (printTree)
                 {
                     PrintAllChildren(@out, r, child, "  ", 1);
@@ -112,7 +112,7 @@ namespace Lucene.Net.Facet.Taxonomy
             int child;
             while ((child = it.Next()) != TaxonomyReader.INVALID_ORDINAL)
             {
-                @out.WriteLine(indent + "/" + r.GetPath(child).components[depth]);
+                @out.WriteLine(indent + "/" + r.GetPath(child).Components[depth]);
                 PrintAllChildren(@out, r, child, indent + "  ", depth + 1);
             }
         }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/Taxonomy/TaxonomyFacets.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/TaxonomyFacets.cs b/Lucene.Net.Facet/Taxonomy/TaxonomyFacets.cs
index b23530d..355ff6c 100644
--- a/Lucene.Net.Facet/Taxonomy/TaxonomyFacets.cs
+++ b/Lucene.Net.Facet/Taxonomy/TaxonomyFacets.cs
@@ -41,44 +41,44 @@ namespace Lucene.Net.Facet.Taxonomy
 
             public virtual int Compare(FacetResult a, FacetResult b)
             {
-                if ((double)a.value > (double)b.value)
+                if (a.Value > b.Value)
                 {
                     return -1;
                 }
-                else if ((double)b.value > (double)a.value)
+                else if (b.Value > a.Value)
                 {
                     return 1;
                 }
                 else
                 {
-                    return a.dim.CompareTo(b.dim);
+                    return a.Dim.CompareTo(b.Dim);
                 }
             }
         }
 
         /// <summary>
         /// Index field name provided to the constructor. </summary>
-        protected internal readonly string IndexFieldName;
+        protected readonly string IndexFieldName;
 
         /// <summary>
         /// {@code TaxonomyReader} provided to the constructor. </summary>
-        protected internal readonly TaxonomyReader TaxoReader;
+        protected readonly TaxonomyReader TaxoReader;
 
         /// <summary>
         /// {@code FacetsConfig} provided to the constructor. </summary>
-        protected internal readonly FacetsConfig Config;
+        protected readonly FacetsConfig Config;
 
         /// <summary>
         /// Maps parent ordinal to its child, or -1 if the parent
         ///  is childless. 
         /// </summary>
-        protected internal readonly int[] Children;
+        protected readonly int[] Children;
 
         /// <summary>
         /// Maps an ordinal to its sibling, or -1 if there is no
         ///  sibling. 
         /// </summary>
-        protected internal readonly int[] Siblings;
+        protected readonly int[] Siblings;
 
         /// <summary>
         /// Sole constructor. 
@@ -101,7 +101,7 @@ namespace Lucene.Net.Facet.Taxonomy
         protected internal virtual DimConfig VerifyDim(string dim)
         {
             DimConfig dimConfig = Config.GetDimConfig(dim);
-            if (!dimConfig.indexFieldName.Equals(IndexFieldName))
+            if (!dimConfig.IndexFieldName.Equals(IndexFieldName))
             {
                 throw new System.ArgumentException("dimension \"" + dim + "\" was not indexed into field \"" + IndexFieldName);
             }
@@ -114,9 +114,9 @@ namespace Lucene.Net.Facet.Taxonomy
             IList<FacetResult> results = new List<FacetResult>();
             while (ord != TaxonomyReader.INVALID_ORDINAL)
             {
-                string dim = TaxoReader.GetPath(ord).components[0];
+                string dim = TaxoReader.GetPath(ord).Components[0];
                 DimConfig dimConfig = Config.GetDimConfig(dim);
-                if (dimConfig.indexFieldName.Equals(IndexFieldName))
+                if (dimConfig.IndexFieldName.Equals(IndexFieldName))
                 {
                     FacetResult result = GetTopChildren(topN, dim);
                     if (result != null)

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/Taxonomy/TaxonomyReader.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/TaxonomyReader.cs b/Lucene.Net.Facet/Taxonomy/TaxonomyReader.cs
index aaee8fe..55c8f27 100644
--- a/Lucene.Net.Facet/Taxonomy/TaxonomyReader.cs
+++ b/Lucene.Net.Facet/Taxonomy/TaxonomyReader.cs
@@ -154,12 +154,12 @@ namespace Lucene.Net.Facet.Taxonomy
         /// the taxonomy has changed.
         /// </summary>
         /// <seealso cref= #openIfChanged(TaxonomyReader) </seealso>
-        protected internal abstract TaxonomyReader DoOpenIfChanged();
+        protected abstract TaxonomyReader DoOpenIfChanged();
 
         /// <summary>
         /// Throws <seealso cref="AlreadyClosedException"/> if this IndexReader is closed
         /// </summary>
-        protected internal void EnsureOpen()
+        protected void EnsureOpen()
         {
             if (RefCount <= 0)
             {

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/Taxonomy/WriterCache/CategoryPathUtils.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/WriterCache/CategoryPathUtils.cs b/Lucene.Net.Facet/Taxonomy/WriterCache/CategoryPathUtils.cs
index 36e5db1..24b0fa2 100644
--- a/Lucene.Net.Facet/Taxonomy/WriterCache/CategoryPathUtils.cs
+++ b/Lucene.Net.Facet/Taxonomy/WriterCache/CategoryPathUtils.cs
@@ -27,15 +27,15 @@
         /// Serializes the given <seealso cref="FacetLabel"/> to the <seealso cref="CharBlockArray"/>. </summary>
         public static void Serialize(FacetLabel cp, CharBlockArray charBlockArray)
         {
-            charBlockArray.Append((char)cp.length);
-            if (cp.length == 0)
+            charBlockArray.Append((char)cp.Length);
+            if (cp.Length == 0)
             {
                 return;
             }
-            for (int i = 0; i < cp.length; i++)
+            for (int i = 0; i < cp.Length; i++)
             {
-                charBlockArray.Append((char)cp.components[i].Length);
-                charBlockArray.Append(cp.components[i]);
+                charBlockArray.Append((char)cp.Components[i].Length);
+                charBlockArray.Append(cp.Components[i]);
             }
         }
 
@@ -68,24 +68,24 @@
         public static bool EqualsToSerialized(FacetLabel cp, CharBlockArray charBlockArray, int offset)
         {
             int n = charBlockArray.CharAt(offset++);
-            if (cp.length != n)
+            if (cp.Length != n)
             {
                 return false;
             }
-            if (cp.length == 0)
+            if (cp.Length == 0)
             {
                 return true;
             }
 
-            for (int i = 0; i < cp.length; i++)
+            for (int i = 0; i < cp.Length; i++)
             {
                 int len = charBlockArray.CharAt(offset++);
-                if (len != cp.components[i].Length)
+                if (len != cp.Components[i].Length)
                 {
                     return false;
                 }
 
-                if (!cp.components[i].Equals(charBlockArray.SubSequence(offset, offset + len)))
+                if (!cp.Components[i].Equals(charBlockArray.SubSequence(offset, offset + len)))
                 {
                     return false;
                 }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/Taxonomy/WriterCache/CharBlockArray.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/WriterCache/CharBlockArray.cs b/Lucene.Net.Facet/Taxonomy/WriterCache/CharBlockArray.cs
index 4695eb0..a6a60e7 100644
--- a/Lucene.Net.Facet/Taxonomy/WriterCache/CharBlockArray.cs
+++ b/Lucene.Net.Facet/Taxonomy/WriterCache/CharBlockArray.cs
@@ -81,7 +81,7 @@ namespace Lucene.Net.Facet.Taxonomy.WriterCache
             this.blocks.Add(this.current);
         }
 
-        internal virtual int blockIndex(int index)
+        internal virtual int BlockIndex(int index)
         {
             return index / blockSize;
         }
@@ -180,7 +180,7 @@ namespace Lucene.Net.Facet.Taxonomy.WriterCache
 
         public char CharAt(int index)
         {
-            Block b = blocks[blockIndex(index)];
+            Block b = blocks[BlockIndex(index)];
             return b.chars[IndexInBlock(index)];
         }
 
@@ -188,7 +188,7 @@ namespace Lucene.Net.Facet.Taxonomy.WriterCache
         {
             int remaining = end - start;
             StringBuilder sb = new StringBuilder(remaining);
-            int blockIdx = blockIndex(start);
+            int blockIdx = BlockIndex(start);
             int indexInBlock = IndexInBlock(start);
             while (remaining > 0)
             {

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/Taxonomy/WriterCache/CollisionMap.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/WriterCache/CollisionMap.cs b/Lucene.Net.Facet/Taxonomy/WriterCache/CollisionMap.cs
index 1adb7b3..000532c 100644
--- a/Lucene.Net.Facet/Taxonomy/WriterCache/CollisionMap.cs
+++ b/Lucene.Net.Facet/Taxonomy/WriterCache/CollisionMap.cs
@@ -1,5 +1,7 @@
 using System;
+using System.Collections;
 using System.Collections.Generic;
+using System.Management.Instrumentation;
 
 namespace Lucene.Net.Facet.Taxonomy.WriterCache
 {
@@ -36,7 +38,7 @@ namespace Lucene.Net.Facet.Taxonomy.WriterCache
         private int size_Renamed;
         private int threshold;
 
-        public class Entry
+        internal class Entry
         {
             internal int offset;
             internal int cid;
@@ -84,8 +86,9 @@ namespace Lucene.Net.Facet.Taxonomy.WriterCache
         }
 
         /// <summary>
-        /// How many slots are allocated. </summary>
-        public virtual int capacity()
+        /// How many slots are allocated. 
+        /// </summary>
+        public virtual int Capacity()
         {
             return this.capacity_Renamed;
         }
@@ -142,7 +145,8 @@ namespace Lucene.Net.Facet.Taxonomy.WriterCache
         }
 
         /// <summary>
-        /// Add another mapping. </summary>
+        /// Add another mapping. 
+        /// </summary>
         public virtual int AddLabel(FacetLabel label, int hash, int cid)
         {
             int bucketIndex = IndexFor(hash, this.capacity_Renamed);
@@ -182,7 +186,7 @@ namespace Lucene.Net.Facet.Taxonomy.WriterCache
             }
         }
 
-        internal virtual EntryIterator entryIterator()
+        internal virtual IEnumerator<CollisionMap.Entry> entryIterator()
         {
             return new EntryIterator(this, entries, size_Renamed);
         }
@@ -221,7 +225,7 @@ namespace Lucene.Net.Facet.Taxonomy.WriterCache
             }
         }
 
-        public class EntryIterator 
+        private class EntryIterator : IEnumerator<Entry>
         {
             private readonly CollisionMap outerInstance;
 
@@ -257,7 +261,7 @@ namespace Lucene.Net.Facet.Taxonomy.WriterCache
                 Entry e = this.next_Renamed;
                 if (e == null)
                 {
-                    throw new IndexOutOfRangeException();
+                    throw new InstanceNotFoundException();
                 }
 
                 Entry n = e.next;
@@ -277,9 +281,28 @@ namespace Lucene.Net.Facet.Taxonomy.WriterCache
                 throw new System.NotSupportedException();
             }
 
-            public Entry Current()
+            public void Dispose()
+            {
+            }
+
+            public bool MoveNext()
+            {
+                if (!HasNext())
+                    return false;
+                Current = Next();
+                return true;
+            }
+
+            public void Reset()
+            {
+                index = 0;
+            }
+
+            public Entry Current { get; private set; }
+
+            object IEnumerator.Current
             {
-                return ents[index];
+                get { return Current; }
             }
         }
 

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/Taxonomy/WriterCache/CompactLabelToOrdinal.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/WriterCache/CompactLabelToOrdinal.cs b/Lucene.Net.Facet/Taxonomy/WriterCache/CompactLabelToOrdinal.cs
index 9f73968..07bd162 100644
--- a/Lucene.Net.Facet/Taxonomy/WriterCache/CompactLabelToOrdinal.cs
+++ b/Lucene.Net.Facet/Taxonomy/WriterCache/CompactLabelToOrdinal.cs
@@ -58,7 +58,7 @@ namespace Lucene.Net.Facet.Taxonomy.WriterCache
         /// Default maximum load factor. </summary>
         public const float DefaultLoadFactor = 0.15f;
 
-        internal const char TERMINATOR_CHAR = (char)0xffff;
+        public const char TERMINATOR_CHAR = (char)0xffff;
         private const int COLLISION = -5;
 
         private HashArray[] hashArrays;
@@ -210,13 +210,14 @@ namespace Lucene.Net.Facet.Taxonomy.WriterCache
             }
 
             CollisionMap oldCollisionMap = this.collisionMap;
-            this.collisionMap = new CollisionMap(oldCollisionMap.capacity(), this.labelRepository);
+            this.collisionMap = new CollisionMap(oldCollisionMap.Capacity(), this.labelRepository);
             this.threshold = (int)(this.capacity * this.loadFactor);
 
             var it = oldCollisionMap.entryIterator();
-            while (it.HasNext())
+
+            while (it.MoveNext())
             {
-                var e = it.Current();
+                var e = it.Current;
                 AddLabelOffset(StringHashCode(this.labelRepository, e.offset), e.cid, e.offset);
             }
         }
@@ -395,7 +396,7 @@ namespace Lucene.Net.Facet.Taxonomy.WriterCache
         /// Opens the file and reloads the CompactLabelToOrdinal. The file it expects
         /// is generated from the <seealso cref="#flush(File)"/> command.
         /// </summary>
-        internal static CompactLabelToOrdinal Open(string file, float loadFactor, int numHashArrays)
+        public static CompactLabelToOrdinal Open(string file, float loadFactor, int numHashArrays)
         {
             /// <summary>
             /// Part of the file is the labelRepository, which needs to be rehashed
@@ -481,7 +482,7 @@ namespace Lucene.Net.Facet.Taxonomy.WriterCache
 
         }
 
-        internal virtual void Flush(Stream stream)
+        public virtual void Flush(Stream stream)
         {
 
             OutputStreamDataOutput dos = new OutputStreamDataOutput(stream);

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/Lucene.Net.Facet/TopOrdAndIntQueue.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/TopOrdAndIntQueue.cs b/Lucene.Net.Facet/TopOrdAndIntQueue.cs
index 63711f2..08bdffb 100644
--- a/Lucene.Net.Facet/TopOrdAndIntQueue.cs
+++ b/Lucene.Net.Facet/TopOrdAndIntQueue.cs
@@ -36,11 +36,11 @@ namespace Lucene.Net.Facet
 
             /// <summary>
             /// Ordinal of the entry. </summary>
-            public int ord;
+            public int Ord;
 
             /// <summary>
             /// Value associated with the ordinal. </summary>
-            public int value;
+            public int Value;
 
             /// <summary>
             /// Default constructor. </summary>
@@ -58,17 +58,17 @@ namespace Lucene.Net.Facet
 
         public override bool LessThan(OrdAndValue a, OrdAndValue b)
         {
-            if (a.value < b.value)
+            if (a.Value < b.Value)
             {
                 return true;
             }
-            else if (a.value > b.value)
+            else if (a.Value > b.Value)
             {
                 return false;
             }
             else
             {
-                return a.ord > b.ord;
+                return a.Ord > b.Ord;
             }
         }
     }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/src/Lucene.Net.Core/Index/SortedSetDocValuesWriter.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Index/SortedSetDocValuesWriter.cs b/src/Lucene.Net.Core/Index/SortedSetDocValuesWriter.cs
index a3fe6a7..6386af6 100644
--- a/src/Lucene.Net.Core/Index/SortedSetDocValuesWriter.cs
+++ b/src/Lucene.Net.Core/Index/SortedSetDocValuesWriter.cs
@@ -200,7 +200,7 @@ namespace Lucene.Net.Index
         {
             AppendingDeltaPackedLongBuffer.Iterator iter = PendingCounts.GetIterator();
 
-            Debug.Assert(maxDoc == Pending.Size(), "MaxDoc: " + maxDoc + ", pending.Size(): " + Pending.Size());
+            //Debug.Assert(maxDoc == Pending.Size(), "MaxDoc: " + maxDoc + ", pending.Size(): " + Pending.Size());
 
             for (int i = 0; i < maxDoc; ++i)
             {

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/src/Lucene.Net.Tests/core/Facet/FacetTestCase.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/FacetTestCase.cs b/src/Lucene.Net.Tests/core/Facet/FacetTestCase.cs
index 49d5f0f..6c2e508 100644
--- a/src/Lucene.Net.Tests/core/Facet/FacetTestCase.cs
+++ b/src/Lucene.Net.Tests/core/Facet/FacetTestCase.cs
@@ -135,7 +135,7 @@ namespace Lucene.Net.Facet
         {
             foreach (FacetResult result in results)
             {
-                SortTies(result.labelValues);
+                SortTies(result.LabelValues);
             }
         }
 
@@ -233,11 +233,11 @@ namespace Lucene.Net.Facet
 
             public virtual int Compare(FacetResult a, FacetResult b)
             {
-                if ((double)a.value > (double)b.value)
+                if ((double)a.Value > (double)b.Value)
                 {
                     return -1;
                 }
-                else if ((double)b.value > (double)a.value)
+                else if ((double)b.Value > (double)a.Value)
                 {
                     return 1;
                 }
@@ -256,17 +256,17 @@ namespace Lucene.Net.Facet
             IDictionary<string, FacetResult> aByDim = new Dictionary<string, FacetResult>();
             for (int i = 0; i < a.Count; i++)
             {
-                Assert.True((float)a[i].value <= lastValue);
-                lastValue = (float)a[i].value;
-                aByDim[a[i].dim] = a[i];
+                Assert.True((float)a[i].Value <= lastValue);
+                lastValue = (float)a[i].Value;
+                aByDim[a[i].Dim] = a[i];
             }
             lastValue = float.PositiveInfinity;
             IDictionary<string, FacetResult> bByDim = new Dictionary<string, FacetResult>();
             for (int i = 0; i < b.Count; i++)
             {
-                bByDim[b[i].dim] = b[i];
-                Assert.True((float)b[i].value <= lastValue);
-                lastValue = (float)b[i].value;
+                bByDim[b[i].Dim] = b[i];
+                Assert.True((float)b[i].Value <= lastValue);
+                lastValue = (float)b[i].Value;
             }
             foreach (string dim in aByDim.Keys)
             {
@@ -277,15 +277,15 @@ namespace Lucene.Net.Facet
         [Test]
         protected internal virtual void AssertFloatValuesEquals(FacetResult a, FacetResult b)
         {
-            Assert.AreEqual(a.dim, b.dim);
-            Assert.True(Arrays.Equals(a.path, b.path));
-            Assert.AreEqual(a.childCount, b.childCount);
-            Assert.AreEqual((float)a.value, (float)b.value, (float)a.value / 1e5);
-            Assert.AreEqual(a.labelValues.Length, b.labelValues.Length);
-            for (int i = 0; i < a.labelValues.Length; i++)
+            Assert.AreEqual(a.Dim, b.Dim);
+            Assert.True(Arrays.Equals(a.Path, b.Path));
+            Assert.AreEqual(a.ChildCount, b.ChildCount);
+            Assert.AreEqual((float)a.Value, (float)b.Value, (float)a.Value / 1e5);
+            Assert.AreEqual(a.LabelValues.Length, b.LabelValues.Length);
+            for (int i = 0; i < a.LabelValues.Length; i++)
             {
-                Assert.AreEqual(a.labelValues[i].label, b.labelValues[i].label);
-                Assert.AreEqual((float)a.labelValues[i].value, (float)b.labelValues[i].value, (float)a.labelValues[i].value / 1e5);
+                Assert.AreEqual(a.LabelValues[i].label, b.LabelValues[i].label);
+                Assert.AreEqual((float)a.LabelValues[i].value, (float)b.LabelValues[i].value, (float)a.LabelValues[i].value / 1e5);
             }
         }
     }


[15/26] lucenenet git commit: first commit of facet porting, failing tests will be fixed in next commits.

Posted by sy...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/Directory/Consts.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/Directory/Consts.cs b/Lucene.Net.Facet/Taxonomy/Directory/Consts.cs
new file mode 100644
index 0000000..5b69985
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/Directory/Consts.cs
@@ -0,0 +1,34 @@
+namespace Lucene.Net.Facet.Taxonomy.Directory
+{
+
+    using BytesRef = Lucene.Net.Util.BytesRef;
+
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// @lucene.experimental
+    /// </summary>
+    internal abstract class Consts
+    {
+        internal const string FULL = "$full_path$";
+        internal const string FIELD_PAYLOADS = "$payloads$";
+        internal const string PAYLOAD_PARENT = "p";
+        internal static readonly BytesRef PAYLOAD_PARENT_BYTES_REF = new BytesRef(PAYLOAD_PARENT);
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/Directory/DirectoryTaxonomyReader.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/Directory/DirectoryTaxonomyReader.cs b/Lucene.Net.Facet/Taxonomy/Directory/DirectoryTaxonomyReader.cs
new file mode 100644
index 0000000..f61187c
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/Directory/DirectoryTaxonomyReader.cs
@@ -0,0 +1,450 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using Lucene.Net.Store;
+
+namespace Lucene.Net.Facet.Taxonomy.Directory
+{
+    using Document = Lucene.Net.Documents.Document;
+    using Lucene.Net.Facet.Taxonomy;
+    using CorruptIndexException = Lucene.Net.Index.CorruptIndexException; // javadocs
+    using DirectoryReader = Lucene.Net.Index.DirectoryReader;
+    using DocsEnum = Lucene.Net.Index.DocsEnum;
+    using IndexWriter = Lucene.Net.Index.IndexWriter;
+    using MultiFields = Lucene.Net.Index.MultiFields;
+    using DocIdSetIterator = Lucene.Net.Search.DocIdSetIterator;
+    using Directory = Lucene.Net.Store.Directory;
+    using BytesRef = Lucene.Net.Util.BytesRef;
+    using IOUtils = Lucene.Net.Util.IOUtils;
+
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// A <seealso cref="TaxonomyReader"/> which retrieves stored taxonomy information from a
+    /// <seealso cref="Directory"/>.
+    /// <P>
+    /// Reading from the on-disk index on every method call is too slow, so this
+    /// implementation employs caching: Some methods cache recent requests and their
+    /// results, while other methods prefetch all the data into memory and then
+    /// provide answers directly from in-memory tables. See the documentation of
+    /// individual methods for comments on their performance.
+    /// 
+    /// @lucene.experimental
+    /// </summary>
+    public class DirectoryTaxonomyReader : TaxonomyReader, IDisposable
+    {
+
+        public class IntClass
+        {
+            public int? IntItem { get; set; }
+        }
+        private const int DEFAULT_CACHE_VALUE = 4000;
+
+        private readonly DirectoryTaxonomyWriter taxoWriter;
+        private readonly long taxoEpoch; // used in doOpenIfChanged
+        private readonly DirectoryReader indexReader;
+
+        // TODO: test DoubleBarrelLRUCache and consider using it instead
+        private LRUHashMap<FacetLabel, IntClass> ordinalCache;
+        private LRUHashMap<int, FacetLabel> categoryCache;
+
+        private volatile TaxonomyIndexArrays taxoArrays;
+
+        /// <summary>
+        /// Called only from <seealso cref="#doOpenIfChanged()"/>. If the taxonomy has been
+        /// recreated, you should pass {@code null} as the caches and parent/children
+        /// arrays.
+        /// </summary>
+        internal DirectoryTaxonomyReader(DirectoryReader indexReader, DirectoryTaxonomyWriter taxoWriter, LRUHashMap<FacetLabel, IntClass> ordinalCache, LRUHashMap<int, FacetLabel> categoryCache, TaxonomyIndexArrays taxoArrays)
+        {
+            this.indexReader = indexReader;
+            this.taxoWriter = taxoWriter;
+            this.taxoEpoch = taxoWriter == null ? -1 : taxoWriter.TaxonomyEpoch;
+
+            // use the same instance of the cache, note the protective code in getOrdinal and getPath
+            this.ordinalCache = ordinalCache == null ? new LRUHashMap<FacetLabel, IntClass>(DEFAULT_CACHE_VALUE) : ordinalCache;
+            this.categoryCache = categoryCache == null ? new LRUHashMap<int, FacetLabel>(DEFAULT_CACHE_VALUE) : categoryCache;
+
+            this.taxoArrays = taxoArrays != null ? new TaxonomyIndexArrays(indexReader, taxoArrays) : null;
+        }
+
+        /// <summary>
+        /// Open for reading a taxonomy stored in a given <seealso cref="Directory"/>.
+        /// </summary>
+        /// <param name="directory">
+        ///          The <seealso cref="Directory"/> in which the taxonomy resides. </param>
+        /// <exception cref="CorruptIndexException">
+        ///           if the Taxonomy is corrupt. </exception>
+        /// <exception cref="IOException">
+        ///           if another error occurred. </exception>
+        public DirectoryTaxonomyReader(Directory directory)
+        {
+            indexReader = OpenIndexReader(directory);
+            taxoWriter = null;
+            taxoEpoch = -1;
+
+            // These are the default cache sizes; they can be configured after
+            // construction with the cache's setMaxSize() method
+
+            ordinalCache = new LRUHashMap<FacetLabel, IntClass>(DEFAULT_CACHE_VALUE);
+            categoryCache = new LRUHashMap<int, FacetLabel>(DEFAULT_CACHE_VALUE);
+        }
+
+        /// <summary>
+        /// Opens a <seealso cref="DirectoryTaxonomyReader"/> over the given
+        /// <seealso cref="DirectoryTaxonomyWriter"/> (for NRT).
+        /// </summary>
+        /// <param name="taxoWriter">
+        ///          The <seealso cref="DirectoryTaxonomyWriter"/> from which to obtain newly
+        ///          added categories, in real-time. </param>
+        public DirectoryTaxonomyReader(DirectoryTaxonomyWriter taxoWriter)
+        {
+            this.taxoWriter = taxoWriter;
+            taxoEpoch = taxoWriter.TaxonomyEpoch;
+            indexReader = OpenIndexReader(taxoWriter.InternalIndexWriter);
+
+            // These are the default cache sizes; they can be configured after
+            // construction with the cache's setMaxSize() method
+
+            ordinalCache = new LRUHashMap<FacetLabel, IntClass>(DEFAULT_CACHE_VALUE);
+            categoryCache = new LRUHashMap<int, FacetLabel>(DEFAULT_CACHE_VALUE);
+        }
+
+        private void InitTaxoArrays()
+        {
+            lock (this)
+            {
+                if (taxoArrays == null)
+                {
+                    // according to Java Concurrency in Practice, this might perform better on
+                    // some JVMs, because the array initialization doesn't happen on the
+                    // volatile member.
+                    TaxonomyIndexArrays tmpArrays = new TaxonomyIndexArrays(indexReader);
+                    taxoArrays = tmpArrays;
+                }
+            }
+        }
+
+        protected internal override void DoClose()
+        {
+            indexReader.Dispose();
+            taxoArrays = null;
+            // do not clear() the caches, as they may be used by other DTR instances.
+            ordinalCache = null;
+            categoryCache = null;
+        }
+
+        /// <summary>
+        /// Implements the opening of a new <seealso cref="DirectoryTaxonomyReader"/> instance if
+        /// the taxonomy has changed.
+        /// 
+        /// <para>
+        /// <b>NOTE:</b> the returned <seealso cref="DirectoryTaxonomyReader"/> shares the
+        /// ordinal and category caches with this reader. This is not expected to cause
+        /// any issues, unless the two instances continue to live. The reader
+        /// guarantees that the two instances cannot affect each other in terms of
+        /// correctness of the caches, however if the size of the cache is changed
+        /// through <seealso cref="#setCacheSize(int)"/>, it will affect both reader instances.
+        /// </para>
+        /// </summary>
+        protected internal override TaxonomyReader DoOpenIfChanged()
+        {
+            EnsureOpen();
+
+            // This works for both NRT and non-NRT readers (i.e. an NRT reader remains NRT).
+            var r2 = DirectoryReader.OpenIfChanged(indexReader);
+            if (r2 == null)
+            {
+                return null; // no changes, nothing to do
+            }
+
+            // check if the taxonomy was recreated
+            bool success = false;
+            try
+            {
+                bool recreated = false;
+                if (taxoWriter == null)
+                {
+                    // not NRT, check epoch from commit data
+                    string t1 = indexReader.IndexCommit.UserData[DirectoryTaxonomyWriter.INDEX_EPOCH];
+                    string t2 = r2.IndexCommit.UserData[DirectoryTaxonomyWriter.INDEX_EPOCH];
+                    if (t1 == null)
+                    {
+                        if (t2 != null)
+                        {
+                            recreated = true;
+                        }
+                    }
+                    else if (!t1.Equals(t2))
+                    {
+                        // t1 != null and t2 cannot be null b/c DirTaxoWriter always puts the commit data.
+                        // it's ok to use String.equals because we require the two epoch values to be the same.
+                        recreated = true;
+                    }
+                }
+                else
+                {
+                    // NRT, compare current taxoWriter.epoch() vs the one that was given at construction
+                    if (taxoEpoch != taxoWriter.TaxonomyEpoch)
+                    {
+                        recreated = true;
+                    }
+                }
+
+                DirectoryTaxonomyReader newtr;
+                if (recreated)
+                {
+                    // if recreated, do not reuse anything from this instace. the information
+                    // will be lazily computed by the new instance when needed.
+                    newtr = new DirectoryTaxonomyReader(r2, taxoWriter, null, null, null);
+                }
+                else
+                {
+                    newtr = new DirectoryTaxonomyReader(r2, taxoWriter, ordinalCache, categoryCache, taxoArrays);
+                }
+
+                success = true;
+                return newtr;
+            }
+            finally
+            {
+                if (!success)
+                {
+                    IOUtils.CloseWhileHandlingException(r2);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Open the <seealso cref="DirectoryReader"/> from this {@link
+        ///  Directory}. 
+        /// </summary>
+        protected virtual DirectoryReader OpenIndexReader(Directory directory)
+        {
+            return DirectoryReader.Open(directory);
+        }
+
+        /// <summary>
+        /// Open the <seealso cref="DirectoryReader"/> from this {@link
+        ///  IndexWriter}. 
+        /// </summary>
+        protected virtual DirectoryReader OpenIndexReader(IndexWriter writer)
+        {
+            return DirectoryReader.Open(writer, false);
+        }
+
+        /// <summary>
+        /// Expert: returns the underlying <seealso cref="DirectoryReader"/> instance that is
+        /// used by this <seealso cref="TaxonomyReader"/>.
+        /// </summary>
+        internal virtual DirectoryReader InternalIndexReader
+        {
+            get
+            {
+                EnsureOpen();
+                return indexReader;
+            }
+        }
+
+        public override ParallelTaxonomyArrays ParallelTaxonomyArrays
+        {
+            get
+            {
+                EnsureOpen();
+                if (taxoArrays == null)
+                {
+                    InitTaxoArrays();
+                }
+                return taxoArrays;
+            }
+        }
+
+        public override IDictionary<string, string> CommitUserData
+        {
+            get
+            {
+                EnsureOpen();
+                return indexReader.IndexCommit.UserData;
+            }
+        }
+
+        public override int GetOrdinal(FacetLabel cp)
+        {
+            EnsureOpen();
+            if (cp.length == 0)
+            {
+                return ROOT_ORDINAL;
+            }
+
+            // First try to find the answer in the LRU cache:
+            lock (ordinalCache)
+            {
+                IntClass res = ordinalCache.Get(cp);
+                if (res != null && res.IntItem != null)
+                {
+                    if ((int)res.IntItem.Value < indexReader.MaxDoc)
+                    {
+                        // Since the cache is shared with DTR instances allocated from
+                        // doOpenIfChanged, we need to ensure that the ordinal is one that
+                        // this DTR instance recognizes.
+                        return (int)res.IntItem.Value;
+                    }
+                    else
+                    {
+                        // if we get here, it means that the category was found in the cache,
+                        // but is not recognized by this TR instance. Therefore there's no
+                        // need to continue search for the path on disk, because we won't find
+                        // it there too.
+                        return TaxonomyReader.INVALID_ORDINAL;
+                    }
+                }
+            }
+
+            // If we're still here, we have a cache miss. We need to fetch the
+            // value from disk, and then also put it in the cache:
+            int ret = TaxonomyReader.INVALID_ORDINAL;
+            DocsEnum docs = MultiFields.GetTermDocsEnum(indexReader, null, Consts.FULL, new BytesRef(FacetsConfig.PathToString(cp.components, cp.length)), 0);
+            if (docs != null && docs.NextDoc() != DocIdSetIterator.NO_MORE_DOCS)
+            {
+                ret = docs.DocID();
+
+                // we only store the fact that a category exists, not its inexistence.
+                // This is required because the caches are shared with new DTR instances
+                // that are allocated from doOpenIfChanged. Therefore, if we only store
+                // information about found categories, we cannot accidently tell a new
+                // generation of DTR that a category does not exist.
+                lock (ordinalCache)
+                {
+                    ordinalCache.Put(cp, new IntClass { IntItem = Convert.ToInt32(ret) });
+                }
+            }
+
+            return ret;
+        }
+
+        public override FacetLabel GetPath(int ordinal)
+        {
+            EnsureOpen();
+
+            // Since the cache is shared with DTR instances allocated from
+            // doOpenIfChanged, we need to ensure that the ordinal is one that this DTR
+            // instance recognizes. Therefore we do this check up front, before we hit
+            // the cache.
+            if (ordinal < 0 || ordinal >= indexReader.MaxDoc)
+            {
+                return null;
+            }
+
+            // TODO: can we use an int-based hash impl, such as IntToObjectMap,
+            // wrapped as LRU?
+            int catIDInteger = Convert.ToInt32(ordinal);
+            lock (categoryCache)
+            {
+                var res = categoryCache.Get(catIDInteger,false);
+                if (res != null)
+                {
+                    return res;
+                }
+            }
+
+            Document doc = indexReader.Document(ordinal);
+            FacetLabel ret = new FacetLabel(FacetsConfig.StringToPath(doc.Get(Consts.FULL)));
+            lock (categoryCache)
+            {
+                categoryCache.Put(catIDInteger, ret);
+            }
+
+            return ret;
+        }
+
+        public override int Size
+        {
+            get
+            {
+                EnsureOpen();
+                return indexReader.NumDocs;
+            }
+        }
+
+        /// <summary>
+        /// setCacheSize controls the maximum allowed size of each of the caches
+        /// used by <seealso cref="#getPath(int)"/> and <seealso cref="#getOrdinal(FacetLabel)"/>.
+        /// <P>
+        /// Currently, if the given size is smaller than the current size of
+        /// a cache, it will not shrink, and rather we be limited to its current
+        /// size. </summary>
+        /// <param name="size"> the new maximum cache size, in number of entries. </param>
+        public virtual int CacheSize
+        {
+            set
+            {
+                EnsureOpen();
+                lock (categoryCache)
+                {
+                    categoryCache.MaxSize = value;
+                }
+                lock (ordinalCache)
+                {
+                    ordinalCache.MaxSize = value;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Returns ordinal -> label mapping, up to the provided
+        ///  max ordinal or number of ordinals, whichever is
+        ///  smaller. 
+        /// </summary>
+        public virtual string ToString(int max)
+        {
+            EnsureOpen();
+            StringBuilder sb = new StringBuilder();
+            int upperl = Math.Min(max, indexReader.MaxDoc);
+            for (int i = 0; i < upperl; i++)
+            {
+                try
+                {
+                    FacetLabel category = this.GetPath(i);
+                    if (category == null)
+                    {
+                        sb.Append(i + ": NULL!! \n");
+                        continue;
+                    }
+                    if (category.length == 0)
+                    {
+                        sb.Append(i + ": EMPTY STRING!! \n");
+                        continue;
+                    }
+                    sb.Append(i + ": " + category.ToString() + "\n");
+                }
+                catch (IOException e)
+                {
+                    throw;
+                }
+            }
+            return sb.ToString();
+        }
+
+        public void Dispose()
+        {
+            Dispose(true);
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/Directory/DirectoryTaxonomyWriter.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/Directory/DirectoryTaxonomyWriter.cs b/Lucene.Net.Facet/Taxonomy/Directory/DirectoryTaxonomyWriter.cs
new file mode 100644
index 0000000..6802b57
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/Directory/DirectoryTaxonomyWriter.cs
@@ -0,0 +1,1191 @@
+using System;
+using System.Collections;
+using System.Diagnostics;
+using System.Collections.Generic;
+using System.IO;
+using Lucene.Net.Analysis.Tokenattributes;
+using Lucene.Net.Store;
+using Lucene.Net.Support;
+
+namespace Lucene.Net.Facet.Taxonomy.Directory
+{
+
+    using TokenStream = Lucene.Net.Analysis.TokenStream;
+    using CharTermAttribute = Lucene.Net.Analysis.Tokenattributes.CharTermAttribute;
+    using PositionIncrementAttribute = Lucene.Net.Analysis.Tokenattributes.PositionIncrementAttribute;
+    using Document = Lucene.Net.Documents.Document;
+    using Field = Lucene.Net.Documents.Field;
+    using FieldType = Lucene.Net.Documents.FieldType;
+    using StringField = Lucene.Net.Documents.StringField;
+    using TextField = Lucene.Net.Documents.TextField;
+    using TaxonomyWriterCache = Lucene.Net.Facet.Taxonomy.WriterCache.TaxonomyWriterCache;
+    using Cl2oTaxonomyWriterCache = Lucene.Net.Facet.Taxonomy.WriterCache.Cl2oTaxonomyWriterCache;
+    using LruTaxonomyWriterCache = Lucene.Net.Facet.Taxonomy.WriterCache.LruTaxonomyWriterCache;
+    using AtomicReader = Lucene.Net.Index.AtomicReader;
+    using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext;
+    using CorruptIndexException = Lucene.Net.Index.CorruptIndexException; // javadocs
+    using DirectoryReader = Lucene.Net.Index.DirectoryReader;
+    using DocsEnum = Lucene.Net.Index.DocsEnum;
+    using IndexReader = Lucene.Net.Index.IndexReader;
+    using IndexWriter = Lucene.Net.Index.IndexWriter;
+    using OpenMode = Lucene.Net.Index.IndexWriterConfig.OpenMode_e;
+    using IndexWriterConfig = Lucene.Net.Index.IndexWriterConfig;
+    using LogByteSizeMergePolicy = Lucene.Net.Index.LogByteSizeMergePolicy;
+    using ReaderManager = Lucene.Net.Index.ReaderManager;
+    using SegmentInfos = Lucene.Net.Index.SegmentInfos;
+    using Terms = Lucene.Net.Index.Terms;
+    using TermsEnum = Lucene.Net.Index.TermsEnum;
+    using TieredMergePolicy = Lucene.Net.Index.TieredMergePolicy;
+    using AlreadyClosedException = Lucene.Net.Store.AlreadyClosedException;
+    using Directory = Lucene.Net.Store.Directory;
+    using LockObtainFailedException = Lucene.Net.Store.LockObtainFailedException; // javadocs
+    using NativeFSLockFactory = Lucene.Net.Store.NativeFSLockFactory;
+    using SimpleFSLockFactory = Lucene.Net.Store.SimpleFSLockFactory;
+    using BytesRef = Lucene.Net.Util.BytesRef;
+    using Version = Lucene.Net.Util.Version;
+
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// <seealso cref="TaxonomyWriter"/> which uses a <seealso cref="Directory"/> to store the taxonomy
+    /// information on disk, and keeps an additional in-memory cache of some or all
+    /// categories.
+    /// <para>
+    /// In addition to the permanently-stored information in the <seealso cref="Directory"/>,
+    /// efficiency dictates that we also keep an in-memory cache of <B>recently
+    /// seen</B> or <B>all</B> categories, so that we do not need to go back to disk
+    /// for every category addition to see which ordinal this category already has,
+    /// if any. A <seealso cref="TaxonomyWriterCache"/> object determines the specific caching
+    /// algorithm used.
+    /// </para>
+    /// <para>
+    /// This class offers some hooks for extending classes to control the
+    /// <seealso cref="IndexWriter"/> instance that is used. See <seealso cref="#openIndexWriter"/>.
+    /// 
+    /// @lucene.experimental
+    /// </para>
+    /// </summary>
+    public class DirectoryTaxonomyWriter : TaxonomyWriter
+    {
+
+        /// <summary>
+        /// Property name of user commit data that contains the index epoch. The epoch
+        /// changes whenever the taxonomy is recreated (i.e. opened with
+        /// <seealso cref="OpenMode#CREATE"/>.
+        /// <para>
+        /// Applications should not use this property in their commit data because it
+        /// will be overridden by this taxonomy writer.
+        /// </para>
+        /// </summary>
+        public const string INDEX_EPOCH = "index.epoch";
+
+        private readonly Directory dir;
+        private readonly IndexWriter indexWriter;
+        private readonly TaxonomyWriterCache cache;
+        private readonly AtomicInteger cacheMisses = new AtomicInteger(0);
+
+        // Records the taxonomy index epoch, updated on replaceTaxonomy as well.
+        private long indexEpoch;
+
+        private SinglePositionTokenStream parentStream = new SinglePositionTokenStream(Consts.PAYLOAD_PARENT);
+        private Field parentStreamField;
+        private Field fullPathField;
+        private int cacheMissesUntilFill = 11;
+        private bool shouldFillCache = true;
+
+        // even though lazily initialized, not volatile so that access to it is
+        // faster. we keep a volatile boolean init instead.
+        private ReaderManager readerManager;
+        private volatile bool initializedReaderManager = false;
+        private volatile bool shouldRefreshReaderManager;
+
+        /// <summary>
+        /// We call the cache "complete" if we know that every category in our
+        /// taxonomy is in the cache. When the cache is <B>not</B> complete, and
+        /// we can't find a category in the cache, we still need to look for it
+        /// in the on-disk index; Therefore when the cache is not complete, we
+        /// need to open a "reader" to the taxonomy index.
+        /// The cache becomes incomplete if it was never filled with the existing
+        /// categories, or if a put() to the cache ever returned true (meaning
+        /// that some of the cached data was cleared).
+        /// </summary>
+        private volatile bool cacheIsComplete;
+        private volatile bool isClosed = false;
+        private volatile TaxonomyIndexArrays taxoArrays;
+        private volatile int nextID;
+
+        /// <summary>
+        /// Reads the commit data from a Directory. </summary>
+        private static IDictionary<string, string> ReadCommitData(Directory dir)
+        {
+            SegmentInfos infos = new SegmentInfos();
+            infos.Read(dir);
+            return infos.UserData;
+        }
+
+        /// <summary>
+        /// Forcibly unlocks the taxonomy in the named directory.
+        /// <P>
+        /// Caution: this should only be used by failure recovery code, when it is
+        /// known that no other process nor thread is in fact currently accessing
+        /// this taxonomy.
+        /// <P>
+        /// This method is unnecessary if your <seealso cref="Directory"/> uses a
+        /// <seealso cref="NativeFSLockFactory"/> instead of the default
+        /// <seealso cref="SimpleFSLockFactory"/>. When the "native" lock is used, a lock
+        /// does not stay behind forever when the process using it dies. 
+        /// </summary>
+        public static void Unlock(Directory directory)
+        {
+            IndexWriter.Unlock(directory);
+        }
+
+        /// <summary>
+        /// Construct a Taxonomy writer.
+        /// </summary>
+        /// <param name="directory">
+        ///    The <seealso cref="Directory"/> in which to store the taxonomy. Note that
+        ///    the taxonomy is written directly to that directory (not to a
+        ///    subdirectory of it). </param>
+        /// <param name="openMode">
+        ///    Specifies how to open a taxonomy for writing: <code>APPEND</code>
+        ///    means open an existing index for append (failing if the index does
+        ///    not yet exist). <code>CREATE</code> means create a new index (first
+        ///    deleting the old one if it already existed).
+        ///    <code>APPEND_OR_CREATE</code> appends to an existing index if there
+        ///    is one, otherwise it creates a new index. </param>
+        /// <param name="cache">
+        ///    A <seealso cref="TaxonomyWriterCache"/> implementation which determines
+        ///    the in-memory caching policy. See for example
+        ///    <seealso cref="LruTaxonomyWriterCache"/> and <seealso cref="Cl2oTaxonomyWriterCache"/>.
+        ///    If null or missing, <seealso cref="#defaultTaxonomyWriterCache()"/> is used. </param>
+        /// <exception cref="CorruptIndexException">
+        ///     if the taxonomy is corrupted. </exception>
+        /// <exception cref="LockObtainFailedException">
+        ///     if the taxonomy is locked by another writer. If it is known
+        ///     that no other concurrent writer is active, the lock might
+        ///     have been left around by an old dead process, and should be
+        ///     removed using <seealso cref="#unlock(Directory)"/>. </exception>
+        /// <exception cref="IOException">
+        ///     if another error occurred. </exception>
+        public DirectoryTaxonomyWriter(Directory directory, OpenMode openMode, TaxonomyWriterCache cache)
+        {
+
+            dir = directory;
+            IndexWriterConfig config = CreateIndexWriterConfig(openMode);
+            indexWriter = OpenIndexWriter(dir, config);
+
+            // verify (to some extent) that merge policy in effect would preserve category docids 
+            if (indexWriter != null)
+            {
+                Debug.Assert(!(indexWriter.Config.MergePolicy is TieredMergePolicy), "for preserving category docids, merging none-adjacent segments is not allowed");
+            }
+
+            // after we opened the writer, and the index is locked, it's safe to check
+            // the commit data and read the index epoch
+            openMode = config.OpenMode.HasValue ? config.OpenMode.Value : OpenMode.CREATE_OR_APPEND;
+            if (!DirectoryReader.IndexExists(directory))
+            {
+                indexEpoch = 1;
+            }
+            else
+            {
+                string epochStr = null;
+                IDictionary<string, string> commitData = ReadCommitData(directory);
+                if (commitData != null && commitData.ContainsKey(INDEX_EPOCH))
+                {
+                    epochStr = commitData[INDEX_EPOCH];
+                }
+                // no commit data, or no epoch in it means an old taxonomy, so set its epoch to 1, for lack
+                // of a better value.
+                indexEpoch = epochStr == null ? 1 : Convert.ToInt64(epochStr, 16);
+            }
+
+            if (openMode == OpenMode.CREATE)
+            {
+                ++indexEpoch;
+            }
+
+            FieldType ft = new FieldType(TextField.TYPE_NOT_STORED);
+            ft.OmitNorms = true;
+            parentStreamField = new Field(Consts.FIELD_PAYLOADS, parentStream, ft);
+            fullPathField = new StringField(Consts.FULL, "", Field.Store.YES);
+
+            if (indexWriter == null)
+                return;
+
+            nextID = indexWriter.MaxDoc;
+
+            if (cache == null)
+            {
+                cache = DefaultTaxonomyWriterCache();
+            }
+            this.cache = cache;
+
+            if (nextID == 0)
+            {
+                cacheIsComplete = true;
+                // Make sure that the taxonomy always contain the root category
+                // with category id 0.
+                AddCategory(new FacetLabel());
+            }
+            else
+            {
+                // There are some categories on the disk, which we have not yet
+                // read into the cache, and therefore the cache is incomplete.
+                // We choose not to read all the categories into the cache now,
+                // to avoid terrible performance when a taxonomy index is opened
+                // to add just a single category. We will do it later, after we
+                // notice a few cache misses.
+                cacheIsComplete = false;
+            }
+        }
+
+        /// <summary>
+        /// Open internal index writer, which contains the taxonomy data.
+        /// <para>
+        /// Extensions may provide their own <seealso cref="IndexWriter"/> implementation or instance. 
+        /// <br><b>NOTE:</b> the instance this method returns will be closed upon calling
+        /// to <seealso cref="#close()"/>.
+        /// <br><b>NOTE:</b> the merge policy in effect must not merge none adjacent segments. See
+        /// comment in <seealso cref="#createIndexWriterConfig(IndexWriterConfig.OpenMode)"/> for the logic behind this.
+        ///  
+        /// </para>
+        /// </summary>
+        /// <seealso cref= #createIndexWriterConfig(IndexWriterConfig.OpenMode)
+        /// </seealso>
+        /// <param name="directory">
+        ///          the <seealso cref="Directory"/> on top of which an <seealso cref="IndexWriter"/>
+        ///          should be opened. </param>
+        /// <param name="config">
+        ///          configuration for the internal index writer. </param>
+        protected virtual IndexWriter OpenIndexWriter(Directory directory, IndexWriterConfig config)
+        {
+            return new IndexWriter(directory, config);
+        }
+
+        /// <summary>
+        /// Create the <seealso cref="IndexWriterConfig"/> that would be used for opening the internal index writer.
+        /// <br>Extensions can configure the <seealso cref="IndexWriter"/> as they see fit,
+        /// including setting a <seealso cref="Lucene.Net.index.MergeScheduler merge-scheduler"/>, or
+        /// <seealso cref="Lucene.Net.index.IndexDeletionPolicy deletion-policy"/>, different RAM size
+        /// etc.<br>
+        /// <br><b>NOTE:</b> internal docids of the configured index must not be altered.
+        /// For that, categories are never deleted from the taxonomy index.
+        /// In addition, merge policy in effect must not merge none adjacent segments.
+        /// </summary>
+        /// <seealso cref= #openIndexWriter(Directory, IndexWriterConfig)
+        /// </seealso>
+        /// <param name="openMode"> see <seealso cref="OpenMode"/> </param>
+        protected virtual IndexWriterConfig CreateIndexWriterConfig(OpenMode openMode)
+        {
+            // TODO: should we use a more optimized Codec, e.g. Pulsing (or write custom)?
+            // The taxonomy has a unique structure, where each term is associated with one document
+
+            // :Post-Release-Update-Version.LUCENE_XY:
+            // Make sure we use a MergePolicy which always merges adjacent segments and thus
+            // keeps the doc IDs ordered as well (this is crucial for the taxonomy index).
+            return (new IndexWriterConfig(Version.LUCENE_48, null)).SetOpenMode(openMode).SetMergePolicy(new LogByteSizeMergePolicy());
+        }
+
+        /// <summary>
+        /// Opens a <seealso cref="ReaderManager"/> from the internal <seealso cref="IndexWriter"/>. 
+        /// </summary>
+        private void InitReaderManager()
+        {
+            if (!initializedReaderManager)
+            {
+                lock (this)
+                {
+                    // verify that the taxo-writer hasn't been closed on us.
+                    EnsureOpen();
+                    if (!initializedReaderManager)
+                    {
+                        readerManager = new ReaderManager(indexWriter, false);
+                        shouldRefreshReaderManager = false;
+                        initializedReaderManager = true;
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Creates a new instance with a default cache as defined by
+        /// <seealso cref="#defaultTaxonomyWriterCache()"/>.
+        /// </summary>
+        public DirectoryTaxonomyWriter(Directory directory, OpenMode openMode = OpenMode.CREATE_OR_APPEND)
+            : this(directory, openMode, DefaultTaxonomyWriterCache())
+        {
+        }
+
+        /// <summary>
+        /// Defines the default <seealso cref="TaxonomyWriterCache"/> to use in constructors
+        /// which do not specify one.
+        /// <P>  
+        /// The current default is <seealso cref="Cl2oTaxonomyWriterCache"/> constructed
+        /// with the parameters (1024, 0.15f, 3), i.e., the entire taxonomy is
+        /// cached in memory while building it.
+        /// </summary>
+        public static TaxonomyWriterCache DefaultTaxonomyWriterCache()
+        {
+            return new Cl2oTaxonomyWriterCache(1024, 0.15f, 3);
+        }
+
+        /// <summary>
+        /// Frees used resources as well as closes the underlying <seealso cref="IndexWriter"/>,
+        /// which commits whatever changes made to it to the underlying
+        /// <seealso cref="Directory"/>.
+        /// </summary>
+        public void Dispose()
+        {
+            lock (this)
+            {
+                if (!isClosed)
+                {
+                    Commit();
+                    DoClose();
+                }
+            }
+        }
+
+        private void DoClose()
+        {
+            indexWriter.Dispose();
+            isClosed = true;
+            CloseResources();
+        }
+
+        /// <summary>
+        /// A hook for extending classes to close additional resources that were used.
+        /// The default implementation closes the <seealso cref="IndexReader"/> as well as the
+        /// <seealso cref="TaxonomyWriterCache"/> instances that were used. <br>
+        /// <b>NOTE:</b> if you override this method, you should include a
+        /// <code>super.closeResources()</code> call in your implementation.
+        /// </summary>
+        protected virtual void CloseResources()
+        {
+            lock (this)
+            {
+                if (initializedReaderManager)
+                {
+                    readerManager.Dispose();
+                    readerManager = null;
+                    initializedReaderManager = false;
+                }
+                if (cache != null)
+                {
+                    cache.Close();
+                }
+            }
+        }
+
+        /// <summary>
+        /// Look up the given category in the cache and/or the on-disk storage,
+        /// returning the category's ordinal, or a negative number in case the
+        /// category does not yet exist in the taxonomy.
+        /// </summary>
+        protected virtual int FindCategory(FacetLabel categoryPath)
+        {
+            lock (this)
+            {
+                // If we can find the category in the cache, or we know the cache is
+                // complete, we can return the response directly from it
+                int res = cache.Get(categoryPath);
+                if (res >= 0 || cacheIsComplete)
+                {
+                    return res;
+                }
+
+                cacheMisses.IncrementAndGet();
+                // After a few cache misses, it makes sense to read all the categories
+                // from disk and into the cache. The reason not to do this on the first
+                // cache miss (or even when opening the writer) is that it will
+                // significantly slow down the case when a taxonomy is opened just to
+                // add one category. The idea only spending a long time on reading
+                // after enough time was spent on cache misses is known as an "online
+                // algorithm".
+                PerhapsFillCache();
+                res = cache.Get(categoryPath);
+                if (res >= 0 || cacheIsComplete)
+                {
+                    // if after filling the cache from the info on disk, the category is in it
+                    // or the cache is complete, return whatever cache.get returned.
+                    return res;
+                }
+
+                // if we get here, it means the category is not in the cache, and it is not
+                // complete, and therefore we must look for the category on disk.
+
+                // We need to get an answer from the on-disk index.
+                InitReaderManager();
+
+                int doc = -1;
+                DirectoryReader reader = readerManager.Acquire();
+                try
+                {
+                    BytesRef catTerm = new BytesRef(FacetsConfig.PathToString(categoryPath.components, categoryPath.length));
+                    TermsEnum termsEnum = null; // reuse
+                    DocsEnum docs = null; // reuse
+                    foreach (AtomicReaderContext ctx in reader.Leaves)
+                    {
+                        Terms terms = ctx.AtomicReader.Terms(Consts.FULL);
+                        if (terms != null)
+                        {
+                            termsEnum = terms.Iterator(termsEnum);
+                            if (termsEnum.SeekExact(catTerm))
+                            {
+                                // liveDocs=null because the taxonomy has no deletes
+                                docs = termsEnum.Docs(null, docs, 0); // freqs not required
+                                // if the term was found, we know it has exactly one document.
+                                doc = docs.NextDoc() + ctx.DocBase;
+                                break;
+                            }
+                        }
+                    }
+                }
+                finally
+                {
+                    readerManager.Release(reader);
+                }
+                if (doc > 0)
+                {
+                    AddToCache(categoryPath, doc);
+                }
+                return doc;
+            }
+        }
+
+        public virtual int AddCategory(FacetLabel categoryPath)
+        {
+            EnsureOpen();
+            // check the cache outside the synchronized block. this results in better
+            // concurrency when categories are there.
+            int res = cache.Get(categoryPath);
+            if (res < 0)
+            {
+                // the category is not in the cache - following code cannot be executed in parallel.
+                lock (this)
+                {
+                    res = FindCategory(categoryPath);
+                    if (res < 0)
+                    {
+                        // This is a new category, and we need to insert it into the index
+                        // (and the cache). Actually, we might also need to add some of
+                        // the category's ancestors before we can add the category itself
+                        // (while keeping the invariant that a parent is always added to
+                        // the taxonomy before its child). internalAddCategory() does all
+                        // this recursively
+                        res = InternalAddCategory(categoryPath);
+                    }
+                }
+            }
+            return res;
+        }
+
+        /// <summary>
+        /// Add a new category into the index (and the cache), and return its new
+        /// ordinal.
+        /// <para>
+        /// Actually, we might also need to add some of the category's ancestors
+        /// before we can add the category itself (while keeping the invariant that a
+        /// parent is always added to the taxonomy before its child). We do this by
+        /// recursion.
+        /// </para>
+        /// </summary>
+        private int InternalAddCategory(FacetLabel cp)
+        {
+            // Find our parent's ordinal (recursively adding the parent category
+            // to the taxonomy if it's not already there). Then add the parent
+            // ordinal as payloads (rather than a stored field; payloads can be
+            // more efficiently read into memory in bulk by LuceneTaxonomyReader)
+            int parent;
+            if (cp.length > 1)
+            {
+                FacetLabel parentPath = cp.Subpath(cp.length - 1);
+                parent = FindCategory(parentPath);
+                if (parent < 0)
+                {
+                    parent = InternalAddCategory(parentPath);
+                }
+            }
+            else if (cp.length == 1)
+            {
+                parent = TaxonomyReader.ROOT_ORDINAL;
+            }
+            else
+            {
+                parent = TaxonomyReader.INVALID_ORDINAL;
+            }
+            int id = AddCategoryDocument(cp, parent);
+
+            return id;
+        }
+
+        /// <summary>
+        /// Verifies that this instance wasn't closed, or throws
+        /// <seealso cref="AlreadyClosedException"/> if it is.
+        /// </summary>
+        protected internal void EnsureOpen()
+        {
+            if (isClosed)
+            {
+                throw new AlreadyClosedException("The taxonomy writer has already been closed");
+            }
+        }
+
+        /// <summary>
+        /// Note that the methods calling addCategoryDocument() are synchornized, so
+        /// this method is effectively synchronized as well.
+        /// </summary>
+        private int AddCategoryDocument(FacetLabel categoryPath, int parent)
+        {
+            // Before Lucene 2.9, position increments >=0 were supported, so we
+            // added 1 to parent to allow the parent -1 (the parent of the root).
+            // Unfortunately, starting with Lucene 2.9, after LUCENE-1542, this is
+            // no longer enough, since 0 is not encoded consistently either (see
+            // comment in SinglePositionTokenStream). But because we must be
+            // backward-compatible with existing indexes, we can't just fix what
+            // we write here (e.g., to write parent+2), and need to do a workaround
+            // in the reader (which knows that anyway only category 0 has a parent
+            // -1).    
+            parentStream.Set(Math.Max(parent + 1, 1));
+            Document d = new Document();
+            d.Add(parentStreamField);
+
+            fullPathField.StringValue = FacetsConfig.PathToString(categoryPath.components, categoryPath.length);
+            d.Add(fullPathField);
+
+            // Note that we do no pass an Analyzer here because the fields that are
+            // added to the Document are untokenized or contains their own TokenStream.
+            // Therefore the IndexWriter's Analyzer has no effect.
+            indexWriter.AddDocument(d);
+            int id = nextID++;
+
+            // added a category document, mark that ReaderManager is not up-to-date
+            shouldRefreshReaderManager = true;
+
+            // also add to the parent array
+            taxoArrays = TaxoArrays.Add(id, parent);
+
+            // NOTE: this line must be executed last, or else the cache gets updated
+            // before the parents array (LUCENE-4596)
+            AddToCache(categoryPath, id);
+
+            return id;
+        }
+
+        private class SinglePositionTokenStream : TokenStream
+        {
+            internal ICharTermAttribute termAtt;
+            internal IPositionIncrementAttribute posIncrAtt;
+            internal bool returned;
+            internal int val;
+            internal readonly string word;
+
+            public SinglePositionTokenStream(string word)
+            {
+                termAtt = AddAttribute<ICharTermAttribute>();
+                posIncrAtt = AddAttribute<IPositionIncrementAttribute>();
+                this.word = word;
+                returned = true;
+            }
+
+            /// <summary>
+            /// Set the value we want to keep, as the position increment.
+            /// Note that when TermPositions.nextPosition() is later used to
+            /// retrieve this value, val-1 will be returned, not val.
+            /// <P>
+            /// IMPORTANT NOTE: Before Lucene 2.9, val>=0 were safe (for val==0,
+            /// the retrieved position would be -1). But starting with Lucene 2.9,
+            /// this unfortunately changed, and only val>0 are safe. val=0 can
+            /// still be used, but don't count on the value you retrieve later
+            /// (it could be 0 or -1, depending on circumstances or versions).
+            /// This change is described in Lucene's JIRA: LUCENE-1542. 
+            /// </summary>
+            public virtual void Set(int val)
+            {
+                this.val = val;
+                returned = false;
+            }
+
+            public override bool IncrementToken()
+            {
+                if (returned)
+                {
+                    return false;
+                }
+                ClearAttributes();
+                posIncrAtt.PositionIncrement = val;
+                termAtt.SetEmpty();
+                termAtt.Append(word);
+                returned = true;
+                return true;
+            }
+        }
+
+        private void AddToCache(FacetLabel categoryPath, int id)
+        {
+            if (cache.Put(categoryPath, id))
+            {
+                // If cache.put() returned true, it means the cache was limited in
+                // size, became full, and parts of it had to be evicted. It is
+                // possible that a relatively-new category that isn't yet visible
+                // to our 'reader' was evicted, and therefore we must now refresh 
+                // the reader.
+                RefreshReaderManager();
+                cacheIsComplete = false;
+            }
+        }
+
+        private void RefreshReaderManager()
+        {
+            lock (this)
+            {
+                // this method is synchronized since it cannot happen concurrently with
+                // addCategoryDocument -- when this method returns, we must know that the
+                // reader manager's state is current. also, it sets shouldRefresh to false, 
+                // and this cannot overlap with addCatDoc too.
+                // NOTE: since this method is sync'ed, it can call maybeRefresh, instead of
+                // maybeRefreshBlocking. If ever this is changed, make sure to change the
+                // call too.
+                if (shouldRefreshReaderManager && initializedReaderManager)
+                {
+                    readerManager.MaybeRefresh();
+                    shouldRefreshReaderManager = false;
+                }
+            }
+        }
+
+        public virtual void Commit()
+        {
+            lock (this)
+            {
+                EnsureOpen();
+                // LUCENE-4972: if we always call setCommitData, we create empty commits
+                string epochStr = null;
+                indexWriter.CommitData.TryGetValue(INDEX_EPOCH, out epochStr);
+                if (epochStr == null || Convert.ToInt64(epochStr, 16) != indexEpoch)
+                {
+                    indexWriter.CommitData = CombinedCommitData(indexWriter.CommitData);
+                }
+                indexWriter.Commit();
+            }
+        }
+
+        /// <summary>
+        /// Combine original user data with the taxonomy epoch. </summary>
+        private IDictionary<string, string> CombinedCommitData(IDictionary<string, string> commitData)
+        {
+            IDictionary<string, string> m = new Dictionary<string, string>();
+            if (commitData != null)
+            {
+                m.PutAll(commitData);
+            }
+            m[INDEX_EPOCH] = Convert.ToString(indexEpoch, 16);
+            return m;
+        }
+
+        public virtual IDictionary<string, string> CommitData
+        {
+            set
+            {
+                indexWriter.CommitData = CombinedCommitData(value);
+            }
+            get
+            {
+                return CombinedCommitData(indexWriter.CommitData);
+            }
+        }
+
+
+        /// <summary>
+        /// prepare most of the work needed for a two-phase commit.
+        /// See <seealso cref="IndexWriter#prepareCommit"/>.
+        /// </summary>
+        public virtual void PrepareCommit()
+        {
+            lock (this)
+            {
+                EnsureOpen();
+                // LUCENE-4972: if we always call setCommitData, we create empty commits
+                string epochStr = indexWriter.CommitData[INDEX_EPOCH];
+                if (epochStr == null || Convert.ToInt64(epochStr, 16) != indexEpoch)
+                {
+                    indexWriter.CommitData = CombinedCommitData(indexWriter.CommitData);
+                }
+                indexWriter.PrepareCommit();
+            }
+        }
+
+        public virtual int Size
+        {
+            get
+            {
+                EnsureOpen();
+                return nextID;
+            }
+        }
+
+        /// <summary>
+        /// Set the number of cache misses before an attempt is made to read the entire
+        /// taxonomy into the in-memory cache.
+        /// <para>
+        /// This taxonomy writer holds an in-memory cache of recently seen categories
+        /// to speed up operation. On each cache-miss, the on-disk index needs to be
+        /// consulted. When an existing taxonomy is opened, a lot of slow disk reads
+        /// like that are needed until the cache is filled, so it is more efficient to
+        /// read the entire taxonomy into memory at once. We do this complete read
+        /// after a certain number (defined by this method) of cache misses.
+        /// </para>
+        /// <para>
+        /// If the number is set to {@code 0}, the entire taxonomy is read into the
+        /// cache on first use, without fetching individual categories first.
+        /// </para>
+        /// <para>
+        /// NOTE: it is assumed that this method is called immediately after the
+        /// taxonomy writer has been created.
+        /// </para>
+        /// </summary>
+        public virtual int CacheMissesUntilFill
+        {
+            set
+            {
+                EnsureOpen();
+                cacheMissesUntilFill = value;
+            }
+        }
+
+        // we need to guarantee that if several threads call this concurrently, only
+        // one executes it, and after it returns, the cache is updated and is either
+        // complete or not.
+        private void PerhapsFillCache()
+        {
+            lock (this)
+            {
+                if (cacheMisses.Get() < cacheMissesUntilFill)
+                {
+                    return;
+                }
+
+                if (!shouldFillCache)
+                {
+                    // we already filled the cache once, there's no need to re-fill it
+                    return;
+                }
+                shouldFillCache = false;
+
+                InitReaderManager();
+
+                bool aborted = false;
+                DirectoryReader reader = readerManager.Acquire();
+                try
+                {
+                    TermsEnum termsEnum = null;
+                    DocsEnum docsEnum = null;
+                    foreach (AtomicReaderContext ctx in reader.Leaves)
+                    {
+                        Terms terms = ctx.AtomicReader.Terms(Consts.FULL);
+                        if (terms != null) // cannot really happen, but be on the safe side
+                        {
+                            termsEnum = terms.Iterator(termsEnum);
+                            while (termsEnum.Next() != null)
+                            {
+                                if (!cache.Full)
+                                {
+                                    BytesRef t = termsEnum.Term();
+                                    // Since we guarantee uniqueness of categories, each term has exactly
+                                    // one document. Also, since we do not allow removing categories (and
+                                    // hence documents), there are no deletions in the index. Therefore, it
+                                    // is sufficient to call next(), and then doc(), exactly once with no
+                                    // 'validation' checks.
+                                    FacetLabel cp = new FacetLabel(FacetsConfig.StringToPath(t.Utf8ToString()));
+                                    docsEnum = termsEnum.Docs(null, docsEnum, DocsEnum.FLAG_NONE);
+                                    bool res = cache.Put(cp, docsEnum.NextDoc() + ctx.DocBase);
+                                    Debug.Assert(!res, "entries should not have been evicted from the cache");
+                                }
+                                else
+                                {
+                                    // the cache is full and the next put() will evict entries from it, therefore abort the iteration.
+                                    aborted = true;
+                                    break;
+                                }
+                            }
+                        }
+                        if (aborted)
+                        {
+                            break;
+                        }
+                    }
+                }
+                finally
+                {
+                    readerManager.Release(reader);
+                }
+
+                cacheIsComplete = !aborted;
+                if (cacheIsComplete)
+                {
+                    lock (this)
+                    {
+                        // everything is in the cache, so no need to keep readerManager open.
+                        // this block is executed in a sync block so that it works well with
+                        // initReaderManager called in parallel.
+                        readerManager.Dispose();
+                        readerManager = null;
+                        initializedReaderManager = false;
+                    }
+                }
+            }
+        }
+
+        private TaxonomyIndexArrays TaxoArrays
+        {
+            get
+            {
+                if (taxoArrays == null)
+                {
+                    lock (this)
+                    {
+                        if (taxoArrays == null)
+                        {
+                            InitReaderManager();
+                            DirectoryReader reader = readerManager.Acquire();
+                            try
+                            {
+                                // according to Java Concurrency, this might perform better on some
+                                // JVMs, since the object initialization doesn't happen on the
+                                // volatile member.
+                                TaxonomyIndexArrays tmpArrays = new TaxonomyIndexArrays(reader);
+                                taxoArrays = tmpArrays;
+                            }
+                            finally
+                            {
+                                readerManager.Release(reader);
+                            }
+                        }
+                    }
+                }
+                return taxoArrays;
+            }
+        }
+
+        public virtual int GetParent(int ordinal)
+        {
+            EnsureOpen();
+            // Note: the following if() just enforces that a user can never ask
+            // for the parent of a nonexistant category - even if the parent array
+            // was allocated bigger than it really needs to be.
+            if (ordinal >= nextID)
+            {
+                throw new System.IndexOutOfRangeException("requested ordinal is bigger than the largest ordinal in the taxonomy");
+            }
+
+            int[] parents = TaxoArrays.Parents();
+            Debug.Assert(ordinal < parents.Length, "requested ordinal (" + ordinal + "); parents.length (" + parents.Length + ") !");
+            return parents[ordinal];
+        }
+
+        /// <summary>
+        /// Takes the categories from the given taxonomy directory, and adds the
+        /// missing ones to this taxonomy. Additionally, it fills the given
+        /// <seealso cref="OrdinalMap"/> with a mapping from the original ordinal to the new
+        /// ordinal.
+        /// </summary>
+        public virtual void AddTaxonomy(Directory taxoDir, OrdinalMap map)
+        {
+            EnsureOpen();
+            DirectoryReader r = DirectoryReader.Open(taxoDir);
+            try
+            {
+                int size = r.NumDocs;
+                OrdinalMap ordinalMap = map;
+                ordinalMap.Size = size;
+                int @base = 0;
+                TermsEnum te = null;
+                DocsEnum docs = null;
+                foreach (AtomicReaderContext ctx in r.Leaves)
+                {
+                    AtomicReader ar = ctx.AtomicReader;
+                    Terms terms = ar.Terms(Consts.FULL);
+                    te = terms.Iterator(te);
+                    while (te.Next() != null)
+                    {
+                        FacetLabel cp = new FacetLabel(FacetsConfig.StringToPath(te.Term().Utf8ToString()));
+                        int ordinal = AddCategory(cp);
+                        docs = te.Docs(null, docs, DocsEnum.FLAG_NONE);
+                        ordinalMap.AddMapping(docs.NextDoc() + @base, ordinal);
+                    }
+                    @base += ar.MaxDoc; // no deletions, so we're ok
+                }
+                ordinalMap.AddDone();
+            }
+            finally
+            {
+                r.Dispose();
+            }
+        }
+
+        /// <summary>
+        /// Mapping from old ordinal to new ordinals, used when merging indexes 
+        /// wit separate taxonomies.
+        /// <para> 
+        /// addToTaxonomies() merges one or more taxonomies into the given taxonomy
+        /// (this). An OrdinalMap is filled for each of the added taxonomies,
+        /// containing the new ordinal (in the merged taxonomy) of each of the
+        /// categories in the old taxonomy.
+        /// <P>  
+        /// There exist two implementations of OrdinalMap: MemoryOrdinalMap and
+        /// DiskOrdinalMap. As their names suggest, the former keeps the map in
+        /// memory and the latter in a temporary disk file. Because these maps will
+        /// later be needed one by one (to remap the counting lists), not all at the
+        /// same time, it is recommended to put the first taxonomy's map in memory,
+        /// and all the rest on disk (later to be automatically read into memory one
+        /// by one, when needed).
+        /// </para>
+        /// </summary>
+        public interface OrdinalMap
+        {
+            /// <summary>
+            /// Set the size of the map. This MUST be called before addMapping().
+            /// It is assumed (but not verified) that addMapping() will then be
+            /// called exactly 'size' times, with different origOrdinals between 0
+            /// and size-1.  
+            /// </summary>
+            int Size { set; }
+
+            /// <summary>
+            /// Record a mapping. </summary>
+            void AddMapping(int origOrdinal, int newOrdinal);
+
+            /// <summary>
+            /// Call addDone() to say that all addMapping() have been done.
+            /// In some implementations this might free some resources. 
+            /// </summary>
+            void AddDone();
+
+            /// <summary>
+            /// Return the map from the taxonomy's original (consecutive) ordinals
+            /// to the new taxonomy's ordinals. If the map has to be read from disk
+            /// and ordered appropriately, it is done when getMap() is called.
+            /// getMap() should only be called once, and only when the map is actually
+            /// needed. Calling it will also free all resources that the map might
+            /// be holding (such as temporary disk space), other than the returned int[].
+            /// </summary>
+            Dictionary<int,int> Map { get; }
+        }
+
+        /// <summary>
+        /// <seealso cref="OrdinalMap"/> maintained in memory
+        /// </summary>
+        public sealed class MemoryOrdinalMap : OrdinalMap
+        {
+            internal Dictionary<int, int> map;
+
+            /// <summary>
+            /// Sole constructor. 
+            /// </summary>
+            public MemoryOrdinalMap()
+            {
+                map = new Dictionary<int,int>();
+            }
+
+            public int Size { set; private get; }
+
+            public void AddMapping(int origOrdinal, int newOrdinal)
+            {
+                map[origOrdinal] = newOrdinal;
+            }
+
+            public void AddDone() // nothing to do
+            {
+            }
+            public Dictionary<int, int> Map
+            {
+                get
+                {
+                    return map;
+                }
+            }
+        }
+
+        /// <summary>
+        /// <seealso cref="OrdinalMap"/> maintained on file system
+        /// </summary>
+        public sealed class DiskOrdinalMap : OrdinalMap
+        {
+            internal string tmpfile;
+            internal OutputStreamDataOutput @out;
+
+            /// <summary>
+            /// Sole constructor. 
+            /// </summary>
+            public DiskOrdinalMap(string tmpfile)
+            {
+                this.tmpfile = tmpfile;
+                var outfs = new FileStream(tmpfile, FileMode.OpenOrCreate, FileAccess.Write);
+                @out = new OutputStreamDataOutput(outfs);
+            }
+
+            public void AddMapping(int origOrdinal, int newOrdinal)
+            {
+                @out.WriteInt(origOrdinal);
+                @out.WriteInt(newOrdinal);
+            }
+
+            public int Size
+            {
+                set
+                {
+                    @out.WriteInt(value);
+                }
+            }
+
+            public void AddDone()
+            {
+                if (@out != null)
+                {
+                    @out.Dispose();
+                    @out = null;
+                }
+            }
+
+            Dictionary<int,int> map = null;
+
+            public Dictionary<int,int> Map
+            {
+                get
+                {
+                    if (map != null)
+                    {
+                        return map;
+                    }
+                    AddDone(); // in case this wasn't previously called
+
+                    var ifs = new FileStream(tmpfile, FileMode.OpenOrCreate, FileAccess.Read);
+                    var @in = new InputStreamDataInput(ifs);
+                    map = new Dictionary<int, int>(@in.ReadInt());
+                    // NOTE: The current code assumes here that the map is complete,
+                    // i.e., every ordinal gets one and exactly one value. Otherwise,
+                    // we may run into an EOF here, or vice versa, not read everything.
+                    for (int i = 0; i < map.Count; i++)
+                    {
+                        int origordinal = @in.ReadInt();
+                        int newordinal = @in.ReadInt();
+                        map[origordinal] = newordinal;
+                    }
+                    @in.Dispose();
+
+                    // Delete the temporary file, which is no longer needed.
+                    if (File.Exists(tmpfile))
+                    {
+                        File.Delete(tmpfile);
+                    }
+                    return map;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Rollback changes to the taxonomy writer and closes the instance. Following
+        /// this method the instance becomes unusable (calling any of its API methods
+        /// will yield an <seealso cref="AlreadyClosedException"/>).
+        /// </summary>
+        public virtual void Rollback()
+        {
+            lock (this)
+            {
+                EnsureOpen();
+                indexWriter.Rollback();
+                DoClose();
+            }
+        }
+
+        /// <summary>
+        /// Replaces the current taxonomy with the given one. This method should
+        /// generally be called in conjunction with
+        /// <seealso cref="IndexWriter#addIndexes(Directory...)"/> to replace both the taxonomy
+        /// as well as the search index content.
+        /// </summary>
+        public virtual void ReplaceTaxonomy(Directory taxoDir)
+        {
+            lock (this)
+            {
+                // replace the taxonomy by doing IW optimized operations
+                indexWriter.DeleteAll();
+                indexWriter.AddIndexes(taxoDir);
+                shouldRefreshReaderManager = true;
+                InitReaderManager(); // ensure that it's initialized
+                RefreshReaderManager();
+                nextID = indexWriter.MaxDoc;
+                taxoArrays = null; // must nullify so that it's re-computed next time it's needed
+
+                // need to clear the cache, so that addCategory won't accidentally return
+                // old categories that are in the cache.
+                cache.Clear();
+                cacheIsComplete = false;
+                shouldFillCache = true;
+                cacheMisses.Set(0);
+
+                // update indexEpoch as a taxonomy replace is just like it has be recreated
+                ++indexEpoch;
+            }
+        }
+
+        /// <summary>
+        /// Returns the <seealso cref="Directory"/> of this taxonomy writer. </summary>
+        public virtual Directory Directory
+        {
+            get
+            {
+                return dir;
+            }
+        }
+
+        /// <summary>
+        /// Used by <seealso cref="DirectoryTaxonomyReader"/> to support NRT.
+        /// <para>
+        /// <b>NOTE:</b> you should not use the obtained <seealso cref="IndexWriter"/> in any
+        /// way, other than opening an IndexReader on it, or otherwise, the taxonomy
+        /// index may become corrupt!
+        /// </para>
+        /// </summary>
+        internal IndexWriter InternalIndexWriter
+        {
+            get
+            {
+                return indexWriter;
+            }
+        }
+
+        /// <summary>
+        /// Expert: returns current index epoch, if this is a
+        /// near-real-time reader.  Used by {@link
+        /// DirectoryTaxonomyReader} to support NRT. 
+        /// 
+        /// @lucene.internal 
+        /// </summary>
+        public long TaxonomyEpoch
+        {
+            get
+            {
+                return indexEpoch;
+            }
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/Directory/TaxonomyIndexArrays.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/Directory/TaxonomyIndexArrays.cs b/Lucene.Net.Facet/Taxonomy/Directory/TaxonomyIndexArrays.cs
new file mode 100644
index 0000000..9a99f4a
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/Directory/TaxonomyIndexArrays.cs
@@ -0,0 +1,252 @@
+using System;
+using System.Diagnostics;
+
+namespace Lucene.Net.Facet.Taxonomy.Directory
+{
+
+    using CorruptIndexException = Lucene.Net.Index.CorruptIndexException;
+    using DocsAndPositionsEnum = Lucene.Net.Index.DocsAndPositionsEnum;
+    using IndexReader = Lucene.Net.Index.IndexReader;
+    using MultiFields = Lucene.Net.Index.MultiFields;
+    using DocIdSetIterator = Lucene.Net.Search.DocIdSetIterator;
+    using ArrayUtil = Lucene.Net.Util.ArrayUtil;
+
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// A <seealso cref="ParallelTaxonomyArrays"/> that are initialized from the taxonomy
+    /// index.
+    /// 
+    /// @lucene.experimental
+    /// </summary>
+    internal class TaxonomyIndexArrays : ParallelTaxonomyArrays
+    {
+
+        private readonly int[] parents_Renamed;
+
+        // the following two arrays are lazily intialized. note that we only keep a
+        // single boolean member as volatile, instead of declaring the arrays
+        // volatile. the code guarantees that only after the boolean is set to true,
+        // the arrays are returned.
+        private volatile bool initializedChildren = false;
+        private int[] children_Renamed, siblings_Renamed;
+
+        /// <summary>
+        /// Used by <seealso cref="#add(int, int)"/> after the array grew. </summary>
+        private TaxonomyIndexArrays(int[] parents)
+        {
+            this.parents_Renamed = parents;
+        }
+
+        public TaxonomyIndexArrays(IndexReader reader)
+        {
+            parents_Renamed = new int[reader.MaxDoc];
+            if (parents_Renamed.Length > 0)
+            {
+                InitParents(reader, 0);
+                // Starting Lucene 2.9, following the change LUCENE-1542, we can
+                // no longer reliably read the parent "-1" (see comment in
+                // LuceneTaxonomyWriter.SinglePositionTokenStream). We have no way
+                // to fix this in indexing without breaking backward-compatibility
+                // with existing indexes, so what we'll do instead is just
+                // hard-code the parent of ordinal 0 to be -1, and assume (as is
+                // indeed the case) that no other parent can be -1.
+                parents_Renamed[0] = TaxonomyReader.INVALID_ORDINAL;
+            }
+        }
+
+        public TaxonomyIndexArrays(IndexReader reader, TaxonomyIndexArrays copyFrom)
+        {
+            Debug.Assert(copyFrom != null);
+
+            // note that copyParents.length may be equal to reader.maxDoc(). this is not a bug
+            // it may be caused if e.g. the taxonomy segments were merged, and so an updated
+            // NRT reader was obtained, even though nothing was changed. this is not very likely
+            // to happen.
+            int[] copyParents = copyFrom.Parents();
+            this.parents_Renamed = new int[reader.MaxDoc];
+            Array.Copy(copyParents, 0, parents_Renamed, 0, copyParents.Length);
+            InitParents(reader, copyParents.Length);
+
+            if (copyFrom.initializedChildren)
+            {
+                InitChildrenSiblings(copyFrom);
+            }
+        }
+
+        private void InitChildrenSiblings(TaxonomyIndexArrays copyFrom)
+        {
+            lock (this)
+            {
+                if (!initializedChildren) // must do this check !
+                {
+                    children_Renamed = new int[parents_Renamed.Length];
+                    siblings_Renamed = new int[parents_Renamed.Length];
+                    if (copyFrom != null)
+                    {
+                        // called from the ctor, after we know copyFrom has initialized children/siblings
+                        Array.Copy(copyFrom.Children(), 0, children_Renamed, 0, copyFrom.Children().Length);
+                        Array.Copy(copyFrom.Siblings(), 0, siblings_Renamed, 0, copyFrom.Siblings().Length);
+                        ComputeChildrenSiblings(copyFrom.parents_Renamed.Length);
+                    }
+                    else
+                    {
+                        ComputeChildrenSiblings(0);
+                    }
+                    initializedChildren = true;
+                }
+            }
+        }
+
+        private void ComputeChildrenSiblings(int first)
+        {
+            // reset the youngest child of all ordinals. while this should be done only
+            // for the leaves, we don't know up front which are the leaves, so we reset
+            // all of them.
+            for (int i = first; i < parents_Renamed.Length; i++)
+            {
+                children_Renamed[i] = TaxonomyReader.INVALID_ORDINAL;
+            }
+
+            // the root category has no parent, and therefore no siblings
+            if (first == 0)
+            {
+                first = 1;
+                siblings_Renamed[0] = TaxonomyReader.INVALID_ORDINAL;
+            }
+
+            for (int i = first; i < parents_Renamed.Length; i++)
+            {
+                // note that parents[i] is always < i, so the right-hand-side of
+                // the following line is already set when we get here
+                siblings_Renamed[i] = children_Renamed[parents_Renamed[i]];
+                children_Renamed[parents_Renamed[i]] = i;
+            }
+        }
+
+        // Read the parents of the new categories
+        private void InitParents(IndexReader reader, int first)
+        {
+            if (reader.MaxDoc == first)
+            {
+                return;
+            }
+
+            // it's ok to use MultiFields because we only iterate on one posting list.
+            // breaking it to loop over the leaves() only complicates code for no
+            // apparent gain.
+            DocsAndPositionsEnum positions = MultiFields.GetTermPositionsEnum(reader, null, Consts.FIELD_PAYLOADS, Consts.PAYLOAD_PARENT_BYTES_REF, DocsAndPositionsEnum.FLAG_PAYLOADS);
+
+            // shouldn't really happen, if it does, something's wrong
+            if (positions == null || positions.Advance(first) == DocIdSetIterator.NO_MORE_DOCS)
+            {
+                throw new CorruptIndexException("Missing parent data for category " + first);
+            }
+
+            int num = reader.MaxDoc;
+            for (int i = first; i < num; i++)
+            {
+                if (positions.DocID() == i)
+                {
+                    if (positions.Freq() == 0) // shouldn't happen
+                    {
+                        throw new CorruptIndexException("Missing parent data for category " + i);
+                    }
+
+                    parents_Renamed[i] = positions.NextPosition();
+
+                    if (positions.NextDoc() == DocIdSetIterator.NO_MORE_DOCS)
+                    {
+                        if (i + 1 < num)
+                        {
+                            throw new CorruptIndexException("Missing parent data for category " + (i + 1));
+                        }
+                        break;
+                    }
+                } // this shouldn't happen
+                else
+                {
+                    throw new CorruptIndexException("Missing parent data for category " + i);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Adds the given ordinal/parent info and returns either a new instance if the
+        /// underlying array had to grow, or this instance otherwise.
+        /// <para>
+        /// <b>NOTE:</b> you should call this method from a thread-safe code.
+        /// </para>
+        /// </summary>
+        internal virtual TaxonomyIndexArrays Add(int ordinal, int parentOrdinal)
+        {
+            if (ordinal >= parents_Renamed.Length)
+            {
+                int[] newarray = ArrayUtil.Grow(parents_Renamed, ordinal + 1);
+                newarray[ordinal] = parentOrdinal;
+                return new TaxonomyIndexArrays(newarray);
+            }
+            parents_Renamed[ordinal] = parentOrdinal;
+            return this;
+        }
+
+        /// <summary>
+        /// Returns the parents array, where {@code parents[i]} denotes the parent of
+        /// category ordinal {@code i}.
+        /// </summary>
+        public override int[] Parents()
+        {
+            return parents_Renamed;
+        }
+
+        /// <summary>
+        /// Returns the children array, where {@code children[i]} denotes the youngest
+        /// child of category ordinal {@code i}. The youngest child is defined as the
+        /// category that was added last to the taxonomy as an immediate child of
+        /// {@code i}.
+        /// </summary>
+        public override int[] Children()
+        {
+            if (!initializedChildren)
+            {
+                InitChildrenSiblings(null);
+            }
+
+            // the array is guaranteed to be populated
+            return children_Renamed;
+        }
+
+        /// <summary>
+        /// Returns the siblings array, where {@code siblings[i]} denotes the sibling
+        /// of category ordinal {@code i}. The sibling is defined as the previous
+        /// youngest child of {@code parents[i]}.
+        /// </summary>
+        public override int[] Siblings()
+        {
+            if (!initializedChildren)
+            {
+                InitChildrenSiblings(null);
+            }
+
+            // the array is guaranteed to be populated
+            return siblings_Renamed;
+        }
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/DocValuesOrdinalsReader.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/DocValuesOrdinalsReader.cs b/Lucene.Net.Facet/Taxonomy/DocValuesOrdinalsReader.cs
new file mode 100644
index 0000000..72a68ed
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/DocValuesOrdinalsReader.cs
@@ -0,0 +1,130 @@
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements.  See the NOTICE file distributed with
+     * this work for additional information regarding copyright ownership.
+     * The ASF licenses this file to You under the Apache License, Version 2.0
+     * (the "License"); you may not use this file except in compliance with
+     * the License.  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     */
+
+    using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext;
+    using BinaryDocValues = Lucene.Net.Index.BinaryDocValues;
+    using DocValues = Lucene.Net.Index.DocValues;
+    using ArrayUtil = Lucene.Net.Util.ArrayUtil;
+    using BytesRef = Lucene.Net.Util.BytesRef;
+    using IntsRef = Lucene.Net.Util.IntsRef;
+
+    /// <summary>
+    /// Decodes ordinals previously indexed into a BinaryDocValues field </summary>
+
+    public class DocValuesOrdinalsReader : OrdinalsReader
+    {
+        private readonly string field;
+
+        /// <summary>
+        /// Default constructor. </summary>
+        public DocValuesOrdinalsReader()
+            : this(FacetsConfig.DEFAULT_INDEX_FIELD_NAME)
+        {
+        }
+
+        /// <summary>
+        /// Create this, with the specified indexed field name. </summary>
+        public DocValuesOrdinalsReader(string field)
+        {
+            this.field = field;
+        }
+
+        public override OrdinalsSegmentReader GetReader(AtomicReaderContext context)
+        {
+            BinaryDocValues values0 = context.AtomicReader.GetBinaryDocValues(field);
+            if (values0 == null)
+            {
+                values0 = DocValues.EMPTY_BINARY;
+            }
+
+            BinaryDocValues values = values0;
+
+            return new OrdinalsSegmentReaderAnonymousInnerClassHelper(this, values);
+        }
+
+        private class OrdinalsSegmentReaderAnonymousInnerClassHelper : OrdinalsSegmentReader
+        {
+            private readonly DocValuesOrdinalsReader outerInstance;
+
+            private BinaryDocValues values;
+
+            public OrdinalsSegmentReaderAnonymousInnerClassHelper(DocValuesOrdinalsReader outerInstance, BinaryDocValues values)
+            {
+                this.outerInstance = outerInstance;
+                this.values = values;
+            }
+
+            public override void Get(int docID, IntsRef ordinals)
+            {
+                BytesRef bytes = new BytesRef();
+                values.Get(docID, bytes);
+                outerInstance.Decode(bytes, ordinals);
+            }
+        }
+
+        public override string IndexFieldName
+        {
+            get
+            {
+                return field;
+            }
+        }
+
+        /// <summary>
+        /// Subclass & override if you change the encoding. </summary>
+        protected virtual void Decode(BytesRef buf, IntsRef ordinals)
+        {
+
+            // grow the buffer up front, even if by a large number of values (buf.length)
+            // that saves the need to check inside the loop for every decoded value if
+            // the buffer needs to grow.
+            if (ordinals.Ints.Length < buf.Length)
+            {
+                ordinals.Ints = ArrayUtil.Grow(ordinals.Ints, buf.Length);
+            }
+
+            ordinals.Offset = 0;
+            ordinals.Length = 0;
+
+            // it is better if the decoding is inlined like so, and not e.g.
+            // in a utility method
+            int upto = buf.Offset + buf.Length;
+            int value = 0;
+            int offset = buf.Offset;
+            int prev = 0;
+            while (offset < upto)
+            {
+                sbyte b = buf.Bytes[offset++];
+                if (b >= 0)
+                {
+                    ordinals.Ints[ordinals.Length] = ((value << 7) | b) + prev;
+                    value = 0;
+                    prev = ordinals.Ints[ordinals.Length];
+                    ordinals.Length++;
+                }
+                else
+                {
+                    value = (value << 7) | (b & 0x7F);
+                }
+            }
+        }
+    }
+
+}
\ No newline at end of file


[19/26] lucenenet git commit: Merge branch 'master' of https://github.com/apache/lucene.net

Posted by sy...@apache.org.
Merge branch 'master' of https://github.com/apache/lucene.net


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

Branch: refs/heads/master
Commit: e01bbc0a2bb822ed8edf7991108610cf588e83cd
Parents: 982eaf6 4f1399f
Author: eladmarg <el...@gmail.com>
Authored: Wed Nov 12 18:08:14 2014 -0800
Committer: eladmarg <el...@gmail.com>
Committed: Wed Nov 12 18:08:14 2014 -0800

----------------------------------------------------------------------
 .../Analysis/CharFilter/BaseCharFilter.cs       |  161 +-
 .../Analysis/CharFilter/MappingCharFilter.cs    |   36 +-
 .../CharFilter/MappingCharFilterFactory.cs      |   20 +-
 .../Analysis/CharFilter/NormalizeCharMap.cs     |    2 +-
 .../Analysis/CommonGrams/CommonGramsFilter.cs   |   67 +-
 .../CommonGrams/CommonGramsFilterFactory.cs     |    4 +-
 .../CommonGrams/CommonGramsQueryFilter.cs       |   26 +-
 .../Compound/CompoundWordTokenFilterBase.cs     |   36 +-
 .../DictionaryCompoundWordTokenFilter.cs        |  259 +-
 .../DictionaryCompoundWordTokenFilterFactory.cs |  136 +-
 .../HyphenationCompoundWordTokenFilter.cs       |  497 +-
 ...HyphenationCompoundWordTokenFilterFactory.cs |   25 +-
 .../Analysis/Core/KeywordAnalyzer.cs            |    2 +-
 .../Analysis/Core/LowerCaseFilter.cs            |    1 +
 .../Analysis/Core/StopAnalyzer.cs               |    5 +-
 .../Analysis/Core/StopFilterFactory.cs          |  286 +-
 .../Analysis/Core/UpperCaseFilter.cs            |    3 +-
 .../Analysis/Core/UpperCaseFilterFactory.cs     |  124 +-
 .../Analysis/Core/WhitespaceAnalyzer.cs         |    2 +-
 .../Miscellaneous/ASCIIFoldingFilter.cs         |   56 +-
 .../Miscellaneous/ASCIIFoldingFilterFactory.cs  |   12 +-
 .../Miscellaneous/CapitalizationFilter.cs       |   31 +-
 .../CapitalizationFilterFactory.cs              |  207 +-
 .../Analysis/Miscellaneous/KeepWordFilter.cs    |   88 +-
 .../Miscellaneous/KeepWordFilterFactory.cs      |  194 +-
 .../Miscellaneous/KeywordMarkerFilterFactory.cs |  172 +-
 .../Miscellaneous/KeywordRepeatFilterFactory.cs |   87 +-
 .../Lucene47WordDelimiterFilter.cs              |    3 +-
 .../Analysis/Miscellaneous/PatternAnalyzer.cs   |    5 +-
 .../RemoveDuplicatesTokenFilter.cs              |  131 +-
 .../RemoveDuplicatesTokenFilterFactory.cs       |   11 +-
 .../Miscellaneous/ScandinavianFoldingFilter.cs  |    6 +-
 .../ScandinavianNormalizationFilter.cs          |    6 +-
 .../Miscellaneous/SetKeywordMarkerFilter.cs     |  107 +-
 .../StemmerOverrideFilterFactory.cs             |  168 +-
 .../Miscellaneous/WordDelimiterFilter.cs        |    3 +-
 .../Miscellaneous/WordDelimiterFilterFactory.cs |   34 +-
 .../Analysis/Ngram/EdgeNGramFilterFactory.cs    |  105 +-
 .../Analysis/Ngram/EdgeNGramTokenFilter.cs      |   97 +-
 .../Analysis/Ngram/EdgeNGramTokenizerFactory.cs |   19 +-
 .../Analysis/Ngram/NGramTokenFilter.cs          |    6 +-
 .../Analysis/Ngram/NGramTokenizer.cs            |    3 +-
 .../DelimitedPayloadTokenFilterFactory.cs       |  147 +-
 .../Analysis/Position/PositionFilter.cs         |   25 +-
 .../Analysis/Position/PositionFilterFactory.cs  |  119 +-
 .../Analysis/Query/QueryAutoStopWordAnalyzer.cs |  401 +-
 .../Analysis/Standard/ClassicAnalyzer.cs        |    8 +-
 .../Analysis/Standard/StandardAnalyzer.cs       |    8 +-
 .../Analysis/Standard/UAX29URLEmailAnalyzer.cs  |    6 +-
 .../Analysis/Synonym/FSTSynonymFilterFactory.cs |   12 +-
 .../Analysis/Synonym/SlowSynonymFilter.cs       |   22 +-
 .../Synonym/SlowSynonymFilterFactory.cs         |    1 +
 .../Analysis/Synonym/SlowSynonymMap.cs          |   10 +-
 .../Analysis/Synonym/SynonymFilterFactory.cs    |  173 +-
 .../Analysis/Util/AnalysisSPILoader.cs          |    9 +-
 .../Analysis/Util/CharArrayMap.cs               |   57 +-
 .../Analysis/Util/CharArraySet.cs               |   54 +-
 .../Analysis/Util/CharacterUtils.cs             |   14 +-
 .../Analysis/Util/ClasspathResourceLoader.cs    |   11 +-
 .../Analysis/Util/ElisionFilter.cs              |  141 +-
 .../Analysis/Util/FilesystemResourceLoader.cs   |    7 +-
 .../Analysis/Util/FilteringTokenFilter.cs       |    6 +-
 .../Analysis/Util/MultiTermAwareComponent.cs    |   67 +-
 .../Analysis/Util/OpenStringBuilder.cs          |    2 +-
 .../Analysis/Util/ResourceLoader.cs             |   87 +-
 .../Analysis/Util/ResourceLoaderAware.cs        |   33 +-
 .../Analysis/Util/RollingCharBuffer.cs          |  364 +-
 .../Analysis/Util/SegmentingTokenizerBase.cs    |  113 +-
 .../Analysis/Util/StemmerUtil.cs                |  271 +-
 .../Analysis/Util/StopwordAnalyzerBase.cs       |   53 +-
 .../Analysis/Util/WordlistLoader.cs             |   39 +-
 .../Collation/CollationKeyAnalyzer.cs           |   19 +-
 .../Memory/DirectDocValuesConsumer.cs           |   16 +-
 .../Memory/DirectPostingsFormat.cs              | 5343 +++++++++---------
 .../Memory/FSTOrdPostingsFormat.cs              |  153 +-
 .../Memory/FSTOrdTermsWriter.cs                 |  837 ++-
 .../Memory/FSTPostingsFormat.cs                 |  152 +-
 src/Lucene.Net.Codecs/Memory/FSTTermsWriter.cs  |   14 +-
 .../Memory/MemoryDocValuesConsumer.cs           |  987 ++--
 .../Memory/MemoryDocValuesProducer.cs           | 1717 +++---
 .../Memory/MemoryPostingsFormat.cs              |  101 +-
 .../Analysis/CachingTokenFilter.cs              |    4 +-
 src/Lucene.Net.Core/Analysis/CharFilter.cs      |    8 +-
 src/Lucene.Net.Core/Analysis/TokenFilter.cs     |   10 +-
 .../Codecs/BlockTreeTermsReader.cs              |   10 +-
 .../Codecs/BlockTreeTermsWriter.cs              |    4 +-
 .../CompressingStoredFieldsReader.cs            |    4 +-
 .../Compressing/CompressingTermVectorsWriter.cs |    2 +-
 .../Codecs/Compressing/CompressionMode.cs       |   14 +-
 .../Codecs/Compressing/Compressor.cs            |    2 +-
 src/Lucene.Net.Core/Codecs/Compressing/LZ4.cs   |   33 +-
 .../Codecs/Lucene3x/Lucene3xFields.cs           |   56 +-
 .../Lucene3x/Lucene3xStoredFieldsReader.cs      |    4 +-
 .../Codecs/Lucene40/Lucene40DocValuesReader.cs  |    2 +-
 .../Codecs/Lucene40/Lucene40PostingsReader.cs   |    2 +-
 .../Lucene40/Lucene40StoredFieldsReader.cs      |    2 +-
 .../Lucene40/Lucene40TermVectorsReader.cs       |   32 +-
 src/Lucene.Net.Core/Codecs/Lucene41/ForUtil.cs  |    7 +-
 .../Codecs/Lucene41/Lucene41PostingsReader.cs   |   16 +-
 .../Codecs/Lucene41/Lucene41PostingsWriter.cs   |    4 +-
 .../Lucene42/Lucene42DocValuesProducer.cs       |    6 +-
 .../Lucene45/Lucene45DocValuesProducer.cs       |    6 +-
 src/Lucene.Net.Core/Codecs/TermVectorsWriter.cs |    2 +-
 .../Document/CompressionTools.cs                |   28 +-
 .../Document/DocumentStoredFieldVisitor.cs      |    2 +-
 src/Lucene.Net.Core/Document/Field.cs           |   30 +-
 src/Lucene.Net.Core/Document/StoredField.cs     |    4 +-
 src/Lucene.Net.Core/Index/AutomatonTermsEnum.cs |   10 +-
 src/Lucene.Net.Core/Index/ByteSliceReader.cs    |    2 +-
 src/Lucene.Net.Core/Index/ByteSliceWriter.cs    |    6 +-
 .../Index/FreqProxTermsWriterPerField.cs        |    2 +-
 .../Index/SortedDocValuesTermsEnum.cs           |    4 +-
 .../Index/SortedSetDocValuesTermsEnum.cs        |    4 +-
 src/Lucene.Net.Core/Index/StoredFieldVisitor.cs |    2 +-
 src/Lucene.Net.Core/Index/TermsHashPerField.cs  |    9 +-
 src/Lucene.Net.Core/Lucene.Net.csproj           |    1 +
 src/Lucene.Net.Core/Search/FieldComparator.cs   |    4 +-
 .../Search/Payloads/PayloadNearQuery.cs         |   27 +-
 .../Search/Payloads/PayloadSpanUtil.cs          |   15 +-
 .../Search/Spans/NearSpansOrdered.cs            |   20 +-
 .../Search/Spans/NearSpansUnordered.cs          |   10 +-
 .../Search/Spans/SpanNearPayloadCheckQuery.cs   |   12 +-
 .../Search/Spans/SpanNotQuery.cs                |   10 +-
 src/Lucene.Net.Core/Search/Spans/SpanOrQuery.cs |    6 +-
 .../Search/Spans/SpanPayloadCheckQuery.cs       |   12 +-
 .../Search/Spans/SpanPositionCheckQuery.cs      |    8 +-
 src/Lucene.Net.Core/Search/Spans/Spans.cs       |    3 +-
 src/Lucene.Net.Core/Search/Spans/TermSpans.cs   |   10 +-
 src/Lucene.Net.Core/Store/DataOutput.cs         |    9 +-
 src/Lucene.Net.Core/Store/FSDirectory.cs        |    2 +-
 .../Store/NativeFSLockFactory.cs                |    2 +-
 src/Lucene.Net.Core/Store/RAMOutputStream.cs    |    2 +-
 .../Compatibility/BinaryWriterDataOutput.cs     |   31 +
 src/Lucene.Net.Core/Support/StringSupport.cs    |    5 +-
 src/Lucene.Net.Core/Util/ArrayUtil.cs           |   13 +-
 .../Util/Automaton/ByteRunAutomaton.cs          |    8 +-
 .../Util/Automaton/CompiledAutomaton.cs         |   10 +-
 .../Util/Automaton/SpecialOperations.cs         |    4 +-
 src/Lucene.Net.Core/Util/BitUtil.cs             |   12 +-
 src/Lucene.Net.Core/Util/ByteBlockPool.cs       |   52 +-
 src/Lucene.Net.Core/Util/BytesRef.cs            |   42 +-
 src/Lucene.Net.Core/Util/BytesRefHash.cs        |   12 +-
 src/Lucene.Net.Core/Util/Fst/BytesRefFSTEnum.cs |    2 +-
 src/Lucene.Net.Core/Util/Fst/FST.cs             |    4 +-
 .../Util/Fst/PositiveIntOutputs.cs              |    1 -
 src/Lucene.Net.Core/Util/Fst/Util.cs            |    2 +-
 .../Util/GrowableByteArrayDataOutput.cs         |    6 +-
 src/Lucene.Net.Core/Util/NumericUtils.cs        |   18 +-
 src/Lucene.Net.Core/Util/OfflineSorter.cs       |  134 +-
 src/Lucene.Net.Core/Util/OpenBitSetIterator.cs  |    2 +-
 src/Lucene.Net.Core/Util/PForDeltaDocIdSet.cs   |   18 +-
 .../Util/Packed/AbstractBlockPackedWriter.cs    |    4 +-
 .../Util/Packed/BlockPackedReaderIterator.cs    |    6 +-
 .../Util/Packed/BulkOperation.cs                |   12 +-
 .../Util/Packed/BulkOperationPacked.cs          |   16 +-
 .../Util/Packed/BulkOperationPacked1.cs         |    8 +-
 .../Util/Packed/BulkOperationPacked10.cs        |    4 +-
 .../Util/Packed/BulkOperationPacked11.cs        |    4 +-
 .../Util/Packed/BulkOperationPacked12.cs        |    4 +-
 .../Util/Packed/BulkOperationPacked13.cs        |    4 +-
 .../Util/Packed/BulkOperationPacked14.cs        |    4 +-
 .../Util/Packed/BulkOperationPacked15.cs        |    4 +-
 .../Util/Packed/BulkOperationPacked16.cs        |    4 +-
 .../Util/Packed/BulkOperationPacked17.cs        |    4 +-
 .../Util/Packed/BulkOperationPacked18.cs        |    4 +-
 .../Util/Packed/BulkOperationPacked19.cs        |    4 +-
 .../Util/Packed/BulkOperationPacked2.cs         |    8 +-
 .../Util/Packed/BulkOperationPacked20.cs        |    4 +-
 .../Util/Packed/BulkOperationPacked21.cs        |    4 +-
 .../Util/Packed/BulkOperationPacked22.cs        |    4 +-
 .../Util/Packed/BulkOperationPacked23.cs        |    4 +-
 .../Util/Packed/BulkOperationPacked24.cs        |    4 +-
 .../Util/Packed/BulkOperationPacked3.cs         |    4 +-
 .../Util/Packed/BulkOperationPacked4.cs         |    8 +-
 .../Util/Packed/BulkOperationPacked5.cs         |    4 +-
 .../Util/Packed/BulkOperationPacked6.cs         |    4 +-
 .../Util/Packed/BulkOperationPacked7.cs         |    4 +-
 .../Util/Packed/BulkOperationPacked8.cs         |    4 +-
 .../Util/Packed/BulkOperationPacked9.cs         |    4 +-
 .../Packed/BulkOperationPackedSingleBlock.cs    |   15 +-
 src/Lucene.Net.Core/Util/Packed/PackedInts.cs   |    8 +-
 .../Util/Packed/PackedReaderIterator.cs         |    6 +-
 src/Lucene.Net.Core/Util/Packed/PackedWriter.cs |    4 +-
 src/Lucene.Net.Core/Util/PagedBytes.cs          |   48 +-
 .../Util/RecyclingByteBlockAllocator.cs         |   14 +-
 src/Lucene.Net.Core/Util/StringHelper.cs        |   14 +-
 src/Lucene.Net.Core/Util/ToStringUtils.cs       |    2 +-
 src/Lucene.Net.Core/Util/UnicodeUtil.cs         |   75 +-
 src/Lucene.Net.Queries/TermsFilter.cs           |    4 +-
 .../Suggest/Analyzing/FreeTextSuggester.cs      |   13 +-
 .../Suggest/Analyzing/SuggestStopFilter.cs      |    6 +-
 .../Suggest/Fst/WFSTCompletionLookup.cs         |   32 +-
 .../Suggest/Jaspell/JaspellLookup.cs            |   38 +-
 .../Analysis/LookaheadTokenFilter.cs            |    2 +-
 .../Analysis/MockCharFilter.cs                  |    2 +-
 .../Analysis/MockFixedLengthPayloadFilter.cs    |    6 +-
 .../Analysis/MockHoleInjectingTokenFilter.cs    |    2 +-
 .../Analysis/MockPayloadAnalyzer.cs             |    2 +-
 .../Analysis/MockTokenFilter.cs                 |    2 +-
 .../Analysis/MockUTF16TermAttributeImpl.cs      |    2 +-
 .../Analysis/MockVariableLengthPayloadFilter.cs |    4 +-
 .../Analysis/ValidatingTokenFilter.cs           |    2 +-
 .../compressing/dummy/DummyCompressingCodec.cs  |    4 +-
 .../lucene42/Lucene42DocValuesConsumer.cs       |    2 +-
 .../Codecs/ramonly/RAMOnlyPostingsFormat.cs     |   12 +-
 .../Index/BaseDocValuesFormatTestCase.cs        |   52 +-
 .../Index/BasePostingsFormatTestCase.cs         |    2 +-
 .../Index/BaseStoredFieldsFormatTestCase.cs     |   22 +-
 src/Lucene.Net.TestFramework/Index/DocHelper.cs |    2 +-
 .../Index/FieldFilterAtomicReader.cs            |    4 +-
 .../Util/LuceneTestCase.cs                      |    4 +-
 src/Lucene.Net.TestFramework/Util/TestUtil.cs   |    7 +
 .../Util/fst/FSTTester.cs                       |    2 +-
 .../core/Analysis/TestCharFilter.cs             |    4 +-
 .../core/Analysis/TestGraphTokenizers.cs        |    2 +-
 .../core/Analysis/TestMockAnalyzer.cs           |    2 +-
 src/Lucene.Net.Tests/core/Analysis/TestToken.cs |    4 +-
 .../Compressing/AbstractTestCompressionMode.cs  |   60 +-
 .../AbstractTestLZ4CompressionMode.cs           |   14 +-
 .../Compressing/TestFastDecompressionMode.cs    |    6 +-
 .../Codecs/Lucene41/TestBlockPostingsFormat3.cs |    2 +-
 .../core/Codecs/Lucene41/TestForUtil.cs         |    4 +-
 .../core/Document/TestBinaryDocument.cs         |    6 +-
 .../core/Document/TestDocument.cs               |    2 +-
 src/Lucene.Net.Tests/core/Document/TestField.cs |    2 +-
 .../core/Index/Test2BBinaryDocValues.cs         |   24 +-
 .../core/Index/Test2BSortedDocValues.cs         |   28 +-
 src/Lucene.Net.Tests/core/Index/Test2BTerms.cs  |    2 +-
 .../core/Index/Test4GBStoredFields.cs           |    4 +-
 .../core/Index/TestBinaryDocValuesUpdates.cs    |    6 +-
 .../core/Index/TestBinaryTerms.cs               |   10 +-
 src/Lucene.Net.Tests/core/Index/TestCodecs.cs   |    4 +-
 .../core/Index/TestConsistentFieldNumbers.cs    |    4 +-
 .../core/Index/TestDirectoryReader.cs           |    2 +-
 .../core/Index/TestDocValuesIndexing.cs         |    8 +-
 .../core/Index/TestDocumentWriter.cs            |    4 +-
 .../core/Index/TestIndexWriter.cs               |    4 +-
 .../core/Index/TestIndexWriterExceptions.cs     |    6 +-
 .../core/Index/TestIndexWriterUnicode.cs        |    4 +-
 .../core/Index/TestIndexableField.cs            |    4 +-
 .../core/Index/TestMultiLevelSkipList.cs        |    4 +-
 src/Lucene.Net.Tests/core/Index/TestPayloads.cs |   52 +-
 .../core/Search/Payloads/PayloadHelper.cs       |    8 +-
 .../Search/Payloads/TestPayloadNearQuery.cs     |    6 +-
 .../Search/Payloads/TestPayloadTermQuery.cs     |   10 +-
 .../core/Search/Spans/JustCompileSearchSpans.cs |    4 +-
 .../core/Search/Spans/MultiSpansWrapper.cs      |   10 +-
 .../core/Search/Spans/TestBasics.cs             |   12 +-
 .../core/Search/Spans/TestPayloadSpans.cs       |   34 +-
 .../core/Search/TestPositionIncrement.cs        |   10 +-
 .../core/Store/TestMultiMMap.cs                 |   26 +-
 .../Util/Automaton/TestDeterminizeLexicon.cs    |    2 +-
 .../core/Util/Automaton/TestUTF32ToUTF8.cs      |    6 +-
 src/Lucene.Net.Tests/core/Util/Fst/Test2BFST.cs |    4 +-
 .../core/Util/Test2BPagedBytes.cs               |    4 +-
 src/Lucene.Net.Tests/core/Util/TestBytesRef.cs  |    6 +-
 .../core/Util/TestBytesRefHash.cs               |    4 +-
 .../core/Util/TestQueryBuilder.cs               |    4 +-
 .../Util/TestRecyclingByteBlockAllocator.cs     |   22 +-
 .../core/Util/TestUnicodeUtil.cs                |    8 +-
 260 files changed, 8111 insertions(+), 8613 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e01bbc0a/src/Lucene.Net.Core/Lucene.Net.csproj
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e01bbc0a/src/Lucene.Net.Core/Search/Spans/NearSpansOrdered.cs
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e01bbc0a/src/Lucene.Net.Core/Util/BytesRefHash.cs
----------------------------------------------------------------------


[25/26] lucenenet git commit: More explicit Equals

Posted by sy...@apache.org.
More explicit Equals


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

Branch: refs/heads/master
Commit: 712ea2e0d7c075f8357b082032dcef5c384d260e
Parents: 58ccdea
Author: Itamar Syn-Hershko <it...@code972.com>
Authored: Fri Nov 14 13:17:04 2014 +0200
Committer: Itamar Syn-Hershko <it...@code972.com>
Committed: Fri Nov 14 13:17:04 2014 +0200

----------------------------------------------------------------------
 src/Lucene.Net.Core/Search/Query.cs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucenenet/blob/712ea2e0/src/Lucene.Net.Core/Search/Query.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Search/Query.cs b/src/Lucene.Net.Core/Search/Query.cs
index 3a66bd3..d553af6 100644
--- a/src/Lucene.Net.Core/Search/Query.cs
+++ b/src/Lucene.Net.Core/Search/Query.cs
@@ -129,10 +129,11 @@ namespace Lucene.Net.Search
 
         public override bool Equals(object obj)
         {
-            if (this == obj)
+            if (ReferenceEquals(obj, this))
             {
                 return true;
             }
+
             var other = obj as Query;
             if (other == null)
             {


[11/26] lucenenet git commit: first commit of facet porting, failing tests will be fixed in next commits.

Posted by sy...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/Range/TestRangeFacetCounts.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Range/TestRangeFacetCounts.cs b/src/Lucene.Net.Tests/core/Facet/Range/TestRangeFacetCounts.cs
new file mode 100644
index 0000000..b9e4ef2
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/Range/TestRangeFacetCounts.cs
@@ -0,0 +1,1174 @@
+using System;
+using System.Diagnostics;
+using System.Collections;
+using System.Collections.Generic;
+using Lucene.Net.Facet.Range;
+using Lucene.Net.Randomized.Generators;
+using Lucene.Net.Support;
+using NUnit.Framework;
+
+namespace Lucene.Net.Facet.Range
+{
+
+    /*
+     * 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 Document = Lucene.Net.Documents.Document;
+    using DoubleDocValuesField = Lucene.Net.Documents.DoubleDocValuesField;
+    using DoubleField = Lucene.Net.Documents.DoubleField;
+    using Field = Lucene.Net.Documents.Field;
+    using FloatDocValuesField = Lucene.Net.Documents.FloatDocValuesField;
+    using FloatField = Lucene.Net.Documents.FloatField;
+    using LongField = Lucene.Net.Documents.LongField;
+    using NumericDocValuesField = Lucene.Net.Documents.NumericDocValuesField;
+    using DrillSidewaysResult = Lucene.Net.Facet.DrillSideways.DrillSidewaysResult;
+    using TaxonomyReader = Lucene.Net.Facet.Taxonomy.TaxonomyReader;
+    using DirectoryTaxonomyReader = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyReader;
+    using DirectoryTaxonomyWriter = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter;
+    using AtomicReader = Lucene.Net.Index.AtomicReader;
+    using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext;
+    using IndexReader = Lucene.Net.Index.IndexReader;
+    using IndexWriterConfig = Lucene.Net.Index.IndexWriterConfig;
+    using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter;
+    using FunctionValues = Lucene.Net.Queries.Function.FunctionValues;
+    using ValueSource = Lucene.Net.Queries.Function.ValueSource;
+    using DoubleDocValues = Lucene.Net.Queries.Function.DocValues.DoubleDocValues;
+    using DoubleFieldSource = Lucene.Net.Queries.Function.ValueSources.DoubleFieldSource;
+    using FloatFieldSource = Lucene.Net.Queries.Function.ValueSources.FloatFieldSource;
+    using LongFieldSource = Lucene.Net.Queries.Function.ValueSources.LongFieldSource;
+    using CachingWrapperFilter = Lucene.Net.Search.CachingWrapperFilter;
+    using DocIdSet = Lucene.Net.Search.DocIdSet;
+    using DocIdSetIterator = Lucene.Net.Search.DocIdSetIterator;
+    using Filter = Lucene.Net.Search.Filter;
+    using IndexSearcher = Lucene.Net.Search.IndexSearcher;
+    using MatchAllDocsQuery = Lucene.Net.Search.MatchAllDocsQuery;
+    using Lucene.Net.Search;
+    using QueryWrapperFilter = Lucene.Net.Search.QueryWrapperFilter;
+    using Directory = Lucene.Net.Store.Directory;
+    using FixedBitSet = Lucene.Net.Util.FixedBitSet;
+    using IOUtils = Lucene.Net.Util.IOUtils;
+    using TestUtil = Lucene.Net.Util.TestUtil;
+
+    [TestFixture]
+    public class TestRangeFacetCounts : FacetTestCase
+    {
+
+        [Test]
+        public virtual void TestBasicLong()
+        {
+            Directory d = NewDirectory();
+            RandomIndexWriter w = new RandomIndexWriter(Random(), d);
+            Document doc = new Document();
+            NumericDocValuesField field = new NumericDocValuesField("field", 0L);
+            doc.Add(field);
+            for (long l = 0; l < 100; l++)
+            {
+                field.LongValue = l;
+                w.AddDocument(doc);
+            }
+
+            // Also add Long.MAX_VALUE
+            field.LongValue = long.MaxValue;
+            w.AddDocument(doc);
+
+            IndexReader r = w.Reader;
+            w.Dispose();
+
+            FacetsCollector fc = new FacetsCollector();
+            IndexSearcher s = NewSearcher(r);
+            s.Search(new MatchAllDocsQuery(), fc);
+
+            Facets facets = new LongRangeFacetCounts("field", fc, new LongRange("less than 10", 0L, true, 10L, false), new LongRange("less than or equal to 10", 0L, true, 10L, true), new LongRange("over 90", 90L, false, 100L, false), new LongRange("90 or above", 90L, true, 100L, false), new LongRange("over 1000", 1000L, false, long.MaxValue, true));
+
+            FacetResult result = facets.GetTopChildren(10, "field");
+
+            Assert.AreEqual("dim=field path=[] value=22 childCount=5\n  less than 10 (10)\n  less than or equal to 10 (11)\n  over 90 (9)\n  90 or above (10)\n  over 1000 (1)\n", result.ToString());
+
+            r.Dispose();
+            d.Dispose();
+        }
+
+        [Test]
+        public virtual void TestUselessRange()
+        {
+            try
+            {
+                new LongRange("useless", 7, true, 6, true);
+                Fail("did not hit expected exception");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+            try
+            {
+                new LongRange("useless", 7, true, 7, false);
+                Fail("did not hit expected exception");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+            try
+            {
+                new DoubleRange("useless", 7.0, true, 6.0, true);
+                Fail("did not hit expected exception");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+            try
+            {
+                new DoubleRange("useless", 7.0, true, 7.0, false);
+                Fail("did not hit expected exception");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+        }
+
+        [Test]
+        public virtual void TestLongMinMax()
+        {
+
+            Directory d = NewDirectory();
+            RandomIndexWriter w = new RandomIndexWriter(Random(), d);
+            Document doc = new Document();
+            NumericDocValuesField field = new NumericDocValuesField("field", 0L);
+            doc.Add(field);
+            field.LongValue = long.MinValue;
+            w.AddDocument(doc);
+            field.LongValue = 0;
+            w.AddDocument(doc);
+            field.LongValue = long.MaxValue;
+            w.AddDocument(doc);
+
+            IndexReader r = w.Reader;
+            w.Dispose();
+
+            FacetsCollector fc = new FacetsCollector();
+            IndexSearcher s = NewSearcher(r);
+            s.Search(new MatchAllDocsQuery(), fc);
+
+            Facets facets = new LongRangeFacetCounts("field", fc, new LongRange("min", long.MinValue, true, long.MinValue, true), new LongRange("max", long.MaxValue, true, long.MaxValue, true), new LongRange("all0", long.MinValue, true, long.MaxValue, true), new LongRange("all1", long.MinValue, false, long.MaxValue, true), new LongRange("all2", long.MinValue, true, long.MaxValue, false), new LongRange("all3", long.MinValue, false, long.MaxValue, false));
+
+            FacetResult result = facets.GetTopChildren(10, "field");
+            Assert.AreEqual("dim=field path=[] value=3 childCount=6\n  min (1)\n  max (1)\n  all0 (3)\n  all1 (2)\n  all2 (2)\n  all3 (1)\n", result.ToString());
+
+            r.Dispose();
+            d.Dispose();
+        }
+
+        [Test]
+        public virtual void TestOverlappedEndStart()
+        {
+            Directory d = NewDirectory();
+            var w = new RandomIndexWriter(Random(), d);
+            Document doc = new Document();
+            NumericDocValuesField field = new NumericDocValuesField("field", 0L);
+            doc.Add(field);
+            for (long l = 0; l < 100; l++)
+            {
+                field.LongValue = l;
+                w.AddDocument(doc);
+            }
+            field.LongValue = long.MaxValue;
+            w.AddDocument(doc);
+
+            IndexReader r = w.Reader;
+            w.Dispose();
+
+            FacetsCollector fc = new FacetsCollector();
+            IndexSearcher s = NewSearcher(r);
+            s.Search(new MatchAllDocsQuery(), fc);
+
+            Facets facets = new LongRangeFacetCounts("field", fc, new LongRange("0-10", 0L, true, 10L, true), new LongRange("10-20", 10L, true, 20L, true), new LongRange("20-30", 20L, true, 30L, true), new LongRange("30-40", 30L, true, 40L, true));
+
+            FacetResult result = facets.GetTopChildren(10, "field");
+            Assert.AreEqual("dim=field path=[] value=41 childCount=4\n  0-10 (11)\n  10-20 (11)\n  20-30 (11)\n  30-40 (11)\n", result.ToString());
+
+            r.Dispose();
+            d.Dispose();
+        }
+
+        /// <summary>
+        /// Tests single request that mixes Range and non-Range
+        ///  faceting, with DrillSideways and taxonomy. 
+        /// </summary>
+        [Test]
+        public virtual void TestMixedRangeAndNonRangeTaxonomy()
+        {
+            Directory d = NewDirectory();
+            RandomIndexWriter w = new RandomIndexWriter(Random(), d);
+            Directory td = NewDirectory();
+            DirectoryTaxonomyWriter tw = new DirectoryTaxonomyWriter(td, IndexWriterConfig.OpenMode_e.CREATE);
+
+            FacetsConfig config = new FacetsConfig();
+
+            for (long l = 0; l < 100; l++)
+            {
+                Document doc = new Document();
+                // For computing range facet counts:
+                doc.Add(new NumericDocValuesField("field", l));
+                // For drill down by numeric range:
+                doc.Add(new LongField("field", l, Field.Store.NO));
+
+                if ((l & 3) == 0)
+                {
+                    doc.Add(new FacetField("dim", "a"));
+                }
+                else
+                {
+                    doc.Add(new FacetField("dim", "b"));
+                }
+                w.AddDocument(config.Build(tw, doc));
+            }
+
+            IndexReader r = w.Reader;
+
+            var tr = new DirectoryTaxonomyReader(tw);
+
+            IndexSearcher s = NewSearcher(r);
+
+            if (VERBOSE)
+            {
+                Console.WriteLine("TEST: searcher=" + s);
+            }
+
+            DrillSideways ds = new DrillSidewaysAnonymousInnerClassHelper(this, s, config, tr);
+
+            // First search, no drill downs:
+            DrillDownQuery ddq = new DrillDownQuery(config);
+            DrillSidewaysResult dsr = ds.Search(null, ddq, 10);
+
+            Assert.AreEqual(100, dsr.Hits.TotalHits);
+            Assert.AreEqual("dim=dim path=[] value=100 childCount=2\n  b (75)\n  a (25)\n", dsr.Facets.GetTopChildren(10, "dim").ToString());
+            Assert.AreEqual("dim=field path=[] value=21 childCount=5\n  less than 10 (10)\n  less than or equal to 10 (11)\n  over 90 (9)\n  90 or above (10)\n  over 1000 (0)\n", dsr.Facets.GetTopChildren(10, "field").ToString());
+
+            // Second search, drill down on dim=b:
+            ddq = new DrillDownQuery(config);
+            ddq.Add("dim", "b");
+            dsr = ds.Search(null, ddq, 10);
+
+            Assert.AreEqual(75, dsr.Hits.TotalHits);
+            Assert.AreEqual("dim=dim path=[] value=100 childCount=2\n  b (75)\n  a (25)\n", dsr.Facets.GetTopChildren(10, "dim").ToString());
+            Assert.AreEqual("dim=field path=[] value=16 childCount=5\n  less than 10 (7)\n  less than or equal to 10 (8)\n  over 90 (7)\n  90 or above (8)\n  over 1000 (0)\n", dsr.Facets.GetTopChildren(10, "field").ToString());
+
+            // Third search, drill down on "less than or equal to 10":
+            ddq = new DrillDownQuery(config);
+            ddq.Add("field", NumericRangeQuery.NewLongRange("field", 0L, 10L, true, true));
+            dsr = ds.Search(null, ddq, 10);
+
+            Assert.AreEqual(11, dsr.Hits.TotalHits);
+            Assert.AreEqual("dim=dim path=[] value=11 childCount=2\n  b (8)\n  a (3)\n", dsr.Facets.GetTopChildren(10, "dim").ToString());
+            Assert.AreEqual("dim=field path=[] value=21 childCount=5\n  less than 10 (10)\n  less than or equal to 10 (11)\n  over 90 (9)\n  90 or above (10)\n  over 1000 (0)\n", dsr.Facets.GetTopChildren(10, "field").ToString());
+            IOUtils.Close(tw, tr, td, w, r, d);
+        }
+
+        private class DrillSidewaysAnonymousInnerClassHelper : DrillSideways
+        {
+            private readonly TestRangeFacetCounts outerInstance;
+
+            private new FacetsConfig config;
+
+            public DrillSidewaysAnonymousInnerClassHelper(TestRangeFacetCounts outerInstance, IndexSearcher s, FacetsConfig config, TaxonomyReader tr)
+                : base(s, config, tr)
+            {
+                this.outerInstance = outerInstance;
+                this.config = config;
+            }
+
+            protected override Facets BuildFacetsResult(FacetsCollector drillDowns, FacetsCollector[] drillSideways, string[] drillSidewaysDims)
+            {
+                FacetsCollector dimFC = drillDowns;
+                FacetsCollector fieldFC = drillDowns;
+                if (drillSideways != null)
+                {
+                    for (int i = 0; i < drillSideways.Length; i++)
+                    {
+                        string dim = drillSidewaysDims[i];
+                        if (dim.Equals("field"))
+                        {
+                            fieldFC = drillSideways[i];
+                        }
+                        else
+                        {
+                            dimFC = drillSideways[i];
+                        }
+                    }
+                }
+
+                IDictionary<string, Facets> byDim = new Dictionary<string, Facets>();
+                byDim["field"] = new LongRangeFacetCounts("field", fieldFC, new LongRange("less than 10", 0L, true, 10L, false), new LongRange("less than or equal to 10", 0L, true, 10L, true), new LongRange("over 90", 90L, false, 100L, false), new LongRange("90 or above", 90L, true, 100L, false), new LongRange("over 1000", 1000L, false, long.MaxValue, false));
+                byDim["dim"] = outerInstance.GetTaxonomyFacetCounts(taxoReader, config, dimFC);
+                return new MultiFacets(byDim, null);
+            }
+
+            protected override bool ScoreSubDocsAtOnce()
+            {
+                return Random().NextBoolean();
+            }
+        }
+
+        [Test]
+        public virtual void TestBasicDouble()
+        {
+            Directory d = NewDirectory();
+            RandomIndexWriter w = new RandomIndexWriter(Random(), d);
+            Document doc = new Document();
+            DoubleDocValuesField field = new DoubleDocValuesField("field", 0.0);
+            doc.Add(field);
+            for (long l = 0; l < 100; l++)
+            {
+                field.DoubleValue = l;
+                w.AddDocument(doc);
+            }
+
+            IndexReader r = w.Reader;
+
+            FacetsCollector fc = new FacetsCollector();
+
+            IndexSearcher s = NewSearcher(r);
+            s.Search(new MatchAllDocsQuery(), fc);
+            Facets facets = new DoubleRangeFacetCounts("field", fc, new DoubleRange("less than 10", 0.0, true, 10.0, false), new DoubleRange("less than or equal to 10", 0.0, true, 10.0, true), new DoubleRange("over 90", 90.0, false, 100.0, false), new DoubleRange("90 or above", 90.0, true, 100.0, false), new DoubleRange("over 1000", 1000.0, false, double.PositiveInfinity, false));
+
+            Assert.AreEqual("dim=field path=[] value=21 childCount=5\n  less than 10 (10)\n  less than or equal to 10 (11)\n  over 90 (9)\n  90 or above (10)\n  over 1000 (0)\n", facets.GetTopChildren(10, "field").ToString());
+
+            IOUtils.Close(w, r, d);
+        }
+
+        [Test]
+        public virtual void TestBasicFloat()
+        {
+            Directory d = NewDirectory();
+            RandomIndexWriter w = new RandomIndexWriter(Random(), d);
+            Document doc = new Document();
+            FloatDocValuesField field = new FloatDocValuesField("field", 0.0f);
+            doc.Add(field);
+            for (long l = 0; l < 100; l++)
+            {
+                field.FloatValue = l;
+                w.AddDocument(doc);
+            }
+
+            IndexReader r = w.Reader;
+
+            FacetsCollector fc = new FacetsCollector();
+
+            IndexSearcher s = NewSearcher(r);
+            s.Search(new MatchAllDocsQuery(), fc);
+
+            Facets facets = new DoubleRangeFacetCounts("field", new FloatFieldSource("field"), fc, new DoubleRange("less than 10", 0.0f, true, 10.0f, false), new DoubleRange("less than or equal to 10", 0.0f, true, 10.0f, true), new DoubleRange("over 90", 90.0f, false, 100.0f, false), new DoubleRange("90 or above", 90.0f, true, 100.0f, false), new DoubleRange("over 1000", 1000.0f, false, double.PositiveInfinity, false));
+
+            Assert.AreEqual("dim=field path=[] value=21 childCount=5\n  less than 10 (10)\n  less than or equal to 10 (11)\n  over 90 (9)\n  90 or above (10)\n  over 1000 (0)\n", facets.GetTopChildren(10, "field").ToString());
+
+            IOUtils.Close(w, r, d);
+        }
+
+        [Test]
+        public virtual void TestRandomLongs()
+        {
+            Directory dir = NewDirectory();
+            var w = new RandomIndexWriter(Random(), dir);
+
+            int numDocs = AtLeast(1000);
+            if (VERBOSE)
+            {
+                Console.WriteLine("TEST: numDocs=" + numDocs);
+            }
+            long[] values = new long[numDocs];
+            long minValue = long.MaxValue;
+            long maxValue = long.MinValue;
+            for (int i = 0; i < numDocs; i++)
+            {
+                Document doc = new Document();
+                long v = Random().NextLong();
+                values[i] = v;
+                doc.Add(new NumericDocValuesField("field", v));
+                doc.Add(new LongField("field", v, Field.Store.NO));
+                w.AddDocument(doc);
+                minValue = Math.Min(minValue, v);
+                maxValue = Math.Max(maxValue, v);
+            }
+            IndexReader r = w.Reader;
+
+            IndexSearcher s = NewSearcher(r);
+            FacetsConfig config = new FacetsConfig();
+
+            int numIters = AtLeast(10);
+            for (int iter = 0; iter < numIters; iter++)
+            {
+                if (VERBOSE)
+                {
+                    Console.WriteLine("TEST: iter=" + iter);
+                }
+                int numRange = TestUtil.NextInt(Random(), 1, 100);
+                LongRange[] ranges = new LongRange[numRange];
+                int[] expectedCounts = new int[numRange];
+                long minAcceptedValue = long.MaxValue;
+                long maxAcceptedValue = long.MinValue;
+                for (int rangeID = 0; rangeID < numRange; rangeID++)
+                {
+                    long min;
+                    if (rangeID > 0 && Random().Next(10) == 7)
+                    {
+                        // Use an existing boundary:
+                        LongRange prevRange = ranges[Random().Next(rangeID)];
+                        if (Random().NextBoolean())
+                        {
+                            min = prevRange.min;
+                        }
+                        else
+                        {
+                            min = prevRange.max;
+                        }
+                    }
+                    else
+                    {
+                        min = Random().NextLong();
+                    }
+                    long max;
+                    if (rangeID > 0 && Random().Next(10) == 7)
+                    {
+                        // Use an existing boundary:
+                        LongRange prevRange = ranges[Random().Next(rangeID)];
+                        if (Random().NextBoolean())
+                        {
+                            max = prevRange.min;
+                        }
+                        else
+                        {
+                            max = prevRange.max;
+                        }
+                    }
+                    else
+                    {
+                        max = Random().NextLong();
+                    }
+
+                    if (min > max)
+                    {
+                        long x = min;
+                        min = max;
+                        max = x;
+                    }
+                    bool minIncl;
+                    bool maxIncl;
+                    if (min == max)
+                    {
+                        minIncl = true;
+                        maxIncl = true;
+                    }
+                    else
+                    {
+                        minIncl = Random().NextBoolean();
+                        maxIncl = Random().NextBoolean();
+                    }
+                    ranges[rangeID] = new LongRange("r" + rangeID, min, minIncl, max, maxIncl);
+                    if (VERBOSE)
+                    {
+                        Console.WriteLine("  range " + rangeID + ": " + ranges[rangeID]);
+                    }
+
+                    // Do "slow but hopefully correct" computation of
+                    // expected count:
+                    for (int i = 0; i < numDocs; i++)
+                    {
+                        bool accept = true;
+                        if (minIncl)
+                        {
+                            accept &= values[i] >= min;
+                        }
+                        else
+                        {
+                            accept &= values[i] > min;
+                        }
+                        if (maxIncl)
+                        {
+                            accept &= values[i] <= max;
+                        }
+                        else
+                        {
+                            accept &= values[i] < max;
+                        }
+                        if (accept)
+                        {
+                            expectedCounts[rangeID]++;
+                            minAcceptedValue = Math.Min(minAcceptedValue, values[i]);
+                            maxAcceptedValue = Math.Max(maxAcceptedValue, values[i]);
+                        }
+                    }
+                }
+
+                FacetsCollector sfc = new FacetsCollector();
+                s.Search(new MatchAllDocsQuery(), sfc);
+                Filter fastMatchFilter;
+                if (Random().NextBoolean())
+                {
+                    if (Random().NextBoolean())
+                    {
+                        fastMatchFilter = NumericRangeFilter.NewLongRange("field", minValue, maxValue, true, true);
+                    }
+                    else
+                    {
+                        fastMatchFilter = NumericRangeFilter.NewLongRange("field", minAcceptedValue, maxAcceptedValue, true, true);
+                    }
+                }
+                else
+                {
+                    fastMatchFilter = null;
+                }
+                ValueSource vs = new LongFieldSource("field");
+                Facets facets = new LongRangeFacetCounts("field", vs, sfc, fastMatchFilter, ranges);
+                FacetResult result = facets.GetTopChildren(10, "field");
+                Assert.AreEqual(numRange, result.labelValues.Length);
+                for (int rangeID = 0; rangeID < numRange; rangeID++)
+                {
+                    if (VERBOSE)
+                    {
+                        Console.WriteLine("  range " + rangeID + " expectedCount=" + expectedCounts[rangeID]);
+                    }
+                    LabelAndValue subNode = result.labelValues[rangeID];
+                    Assert.AreEqual("r" + rangeID, subNode.label);
+                    Assert.AreEqual(expectedCounts[rangeID], (int)subNode.value);
+
+                    LongRange range = ranges[rangeID];
+
+                    // Test drill-down:
+                    DrillDownQuery ddq = new DrillDownQuery(config);
+                    if (Random().NextBoolean())
+                    {
+                        if (Random().NextBoolean())
+                        {
+                            ddq.Add("field", NumericRangeFilter.NewLongRange("field", range.min, range.max, range.minInclusive, range.maxInclusive));
+                        }
+                        else
+                        {
+                            ddq.Add("field", NumericRangeQuery.NewLongRange("field", range.min, range.max, range.minInclusive, range.maxInclusive));
+                        }
+                    }
+                    else
+                    {
+                        ddq.Add("field", range.GetFilter(fastMatchFilter, vs));
+                    }
+                    Assert.AreEqual(expectedCounts[rangeID], s.Search(ddq, 10).TotalHits);
+                }
+            }
+
+            IOUtils.Close(w, r, dir);
+        }
+
+        [Test]
+        public virtual void TestRandomFloats()
+        {
+            Directory dir = NewDirectory();
+            RandomIndexWriter w = new RandomIndexWriter(Random(), dir);
+
+            int numDocs = AtLeast(1000);
+            float[] values = new float[numDocs];
+            float minValue = float.PositiveInfinity;
+            float maxValue = float.NegativeInfinity;
+            for (int i = 0; i < numDocs; i++)
+            {
+                Document doc = new Document();
+                float v = Random().NextFloat();
+                values[i] = v;
+                doc.Add(new FloatDocValuesField("field", v));
+                doc.Add(new FloatField("field", v, Field.Store.NO));
+                w.AddDocument(doc);
+                minValue = Math.Min(minValue, v);
+                maxValue = Math.Max(maxValue, v);
+            }
+            IndexReader r = w.Reader;
+
+            IndexSearcher s = NewSearcher(r);
+            FacetsConfig config = new FacetsConfig();
+
+            int numIters = AtLeast(10);
+            for (int iter = 0; iter < numIters; iter++)
+            {
+                if (VERBOSE)
+                {
+                    Console.WriteLine("TEST: iter=" + iter);
+                }
+                int numRange = TestUtil.NextInt(Random(), 1, 5);
+                DoubleRange[] ranges = new DoubleRange[numRange];
+                int[] expectedCounts = new int[numRange];
+                float minAcceptedValue = float.PositiveInfinity;
+                float maxAcceptedValue = float.NegativeInfinity;
+                if (VERBOSE)
+                {
+                    Console.WriteLine("TEST: " + numRange + " ranges");
+                }
+                for (int rangeID = 0; rangeID < numRange; rangeID++)
+                {
+                    double min;
+                    if (rangeID > 0 && Random().Next(10) == 7)
+                    {
+                        // Use an existing boundary:
+                        DoubleRange prevRange = ranges[Random().Next(rangeID)];
+                        if (Random().NextBoolean())
+                        {
+                            min = prevRange.min;
+                        }
+                        else
+                        {
+                            min = prevRange.max;
+                        }
+                    }
+                    else
+                    {
+                        min = Random().NextDouble();
+                    }
+                    double max;
+                    if (rangeID > 0 && Random().Next(10) == 7)
+                    {
+                        // Use an existing boundary:
+                        DoubleRange prevRange = ranges[Random().Next(rangeID)];
+                        if (Random().NextBoolean())
+                        {
+                            max = prevRange.min;
+                        }
+                        else
+                        {
+                            max = prevRange.max;
+                        }
+                    }
+                    else
+                    {
+                        max = Random().NextDouble();
+                    }
+
+                    if (min > max)
+                    {
+                        double x = min;
+                        min = max;
+                        max = x;
+                    }
+
+                    // Must truncate to float precision so that the
+                    // drill-down counts (which use NRQ.newFloatRange)
+                    // are correct:
+                    min = (float)min;
+                    max = (float)max;
+
+                    bool minIncl;
+                    bool maxIncl;
+                    if (min == max)
+                    {
+                        minIncl = true;
+                        maxIncl = true;
+                    }
+                    else
+                    {
+                        minIncl = Random().NextBoolean();
+                        maxIncl = Random().NextBoolean();
+                    }
+                    ranges[rangeID] = new DoubleRange("r" + rangeID, min, minIncl, max, maxIncl);
+
+                    if (VERBOSE)
+                    {
+                        Console.WriteLine("TEST:   range " + rangeID + ": " + ranges[rangeID]);
+                    }
+
+                    // Do "slow but hopefully correct" computation of
+                    // expected count:
+                    for (int i = 0; i < numDocs; i++)
+                    {
+                        bool accept = true;
+                        if (minIncl)
+                        {
+                            accept &= values[i] >= min;
+                        }
+                        else
+                        {
+                            accept &= values[i] > min;
+                        }
+                        if (maxIncl)
+                        {
+                            accept &= values[i] <= max;
+                        }
+                        else
+                        {
+                            accept &= values[i] < max;
+                        }
+                        if (VERBOSE)
+                        {
+                            Console.WriteLine("TEST:   check doc=" + i + " val=" + values[i] + " accept=" + accept);
+                        }
+                        if (accept)
+                        {
+                            expectedCounts[rangeID]++;
+                            minAcceptedValue = Math.Min(minAcceptedValue, values[i]);
+                            maxAcceptedValue = Math.Max(maxAcceptedValue, values[i]);
+                        }
+                    }
+                }
+
+                FacetsCollector sfc = new FacetsCollector();
+                s.Search(new MatchAllDocsQuery(), sfc);
+                Filter fastMatchFilter;
+                if (Random().NextBoolean())
+                {
+                    if (Random().NextBoolean())
+                    {
+                        fastMatchFilter = NumericRangeFilter.NewFloatRange("field", minValue, maxValue, true, true);
+                    }
+                    else
+                    {
+                        fastMatchFilter = NumericRangeFilter.NewFloatRange("field", minAcceptedValue, maxAcceptedValue, true, true);
+                    }
+                }
+                else
+                {
+                    fastMatchFilter = null;
+                }
+                ValueSource vs = new FloatFieldSource("field");
+                Facets facets = new DoubleRangeFacetCounts("field", vs, sfc, fastMatchFilter, ranges);
+                FacetResult result = facets.GetTopChildren(10, "field");
+                Assert.AreEqual(numRange, result.labelValues.Length);
+                for (int rangeID = 0; rangeID < numRange; rangeID++)
+                {
+                    if (VERBOSE)
+                    {
+                        Console.WriteLine("TEST: verify range " + rangeID + " expectedCount=" + expectedCounts[rangeID]);
+                    }
+                    LabelAndValue subNode = result.labelValues[rangeID];
+                    Assert.AreEqual("r" + rangeID, subNode.label);
+                    Assert.AreEqual(expectedCounts[rangeID], (int)subNode.value);
+
+                    DoubleRange range = ranges[rangeID];
+
+                    // Test drill-down:
+                    DrillDownQuery ddq = new DrillDownQuery(config);
+                    if (Random().NextBoolean())
+                    {
+                        if (Random().NextBoolean())
+                        {
+                            ddq.Add("field", NumericRangeFilter.NewFloatRange("field", (float)range.min, (float)range.max, range.minInclusive, range.maxInclusive));
+                        }
+                        else
+                        {
+                            ddq.Add("field", NumericRangeQuery.NewFloatRange("field", (float)range.min, (float)range.max, range.minInclusive, range.maxInclusive));
+                        }
+                    }
+                    else
+                    {
+                        ddq.Add("field", range.GetFilter(fastMatchFilter, vs));
+                    }
+                    Assert.AreEqual(expectedCounts[rangeID], s.Search(ddq, 10).TotalHits);
+                }
+            }
+
+            IOUtils.Close(w, r, dir);
+        }
+
+        [Test]
+        public virtual void TestRandomDoubles()
+        {
+            Directory dir = NewDirectory();
+            RandomIndexWriter w = new RandomIndexWriter(Random(), dir);
+
+            int numDocs = AtLeast(1000);
+            double[] values = new double[numDocs];
+            double minValue = double.PositiveInfinity;
+            double maxValue = double.NegativeInfinity;
+            for (int i = 0; i < numDocs; i++)
+            {
+                Document doc = new Document();
+                double v = Random().NextDouble();
+                values[i] = v;
+                doc.Add(new DoubleDocValuesField("field", v));
+                doc.Add(new DoubleField("field", v, Field.Store.NO));
+                w.AddDocument(doc);
+                minValue = Math.Min(minValue, v);
+                maxValue = Math.Max(maxValue, v);
+            }
+            IndexReader r = w.Reader;
+
+            IndexSearcher s = NewSearcher(r);
+            FacetsConfig config = new FacetsConfig();
+
+            int numIters = AtLeast(10);
+            for (int iter = 0; iter < numIters; iter++)
+            {
+                if (VERBOSE)
+                {
+                    Console.WriteLine("TEST: iter=" + iter);
+                }
+                int numRange = TestUtil.NextInt(Random(), 1, 5);
+                DoubleRange[] ranges = new DoubleRange[numRange];
+                int[] expectedCounts = new int[numRange];
+                double minAcceptedValue = double.PositiveInfinity;
+                double maxAcceptedValue = double.NegativeInfinity;
+                for (int rangeID = 0; rangeID < numRange; rangeID++)
+                {
+                    double min;
+                    if (rangeID > 0 && Random().Next(10) == 7)
+                    {
+                        // Use an existing boundary:
+                        DoubleRange prevRange = ranges[Random().Next(rangeID)];
+                        if (Random().NextBoolean())
+                        {
+                            min = prevRange.min;
+                        }
+                        else
+                        {
+                            min = prevRange.max;
+                        }
+                    }
+                    else
+                    {
+                        min = Random().NextDouble();
+                    }
+                    double max;
+                    if (rangeID > 0 && Random().Next(10) == 7)
+                    {
+                        // Use an existing boundary:
+                        DoubleRange prevRange = ranges[Random().Next(rangeID)];
+                        if (Random().NextBoolean())
+                        {
+                            max = prevRange.min;
+                        }
+                        else
+                        {
+                            max = prevRange.max;
+                        }
+                    }
+                    else
+                    {
+                        max = Random().NextDouble();
+                    }
+
+                    if (min > max)
+                    {
+                        double x = min;
+                        min = max;
+                        max = x;
+                    }
+
+                    bool minIncl;
+                    bool maxIncl;
+                    if (min == max)
+                    {
+                        minIncl = true;
+                        maxIncl = true;
+                    }
+                    else
+                    {
+                        minIncl = Random().NextBoolean();
+                        maxIncl = Random().NextBoolean();
+                    }
+                    ranges[rangeID] = new DoubleRange("r" + rangeID, min, minIncl, max, maxIncl);
+
+                    // Do "slow but hopefully correct" computation of
+                    // expected count:
+                    for (int i = 0; i < numDocs; i++)
+                    {
+                        bool accept = true;
+                        if (minIncl)
+                        {
+                            accept &= values[i] >= min;
+                        }
+                        else
+                        {
+                            accept &= values[i] > min;
+                        }
+                        if (maxIncl)
+                        {
+                            accept &= values[i] <= max;
+                        }
+                        else
+                        {
+                            accept &= values[i] < max;
+                        }
+                        if (accept)
+                        {
+                            expectedCounts[rangeID]++;
+                            minAcceptedValue = Math.Min(minAcceptedValue, values[i]);
+                            maxAcceptedValue = Math.Max(maxAcceptedValue, values[i]);
+                        }
+                    }
+                }
+
+                FacetsCollector sfc = new FacetsCollector();
+                s.Search(new MatchAllDocsQuery(), sfc);
+                Filter fastMatchFilter;
+                if (Random().NextBoolean())
+                {
+                    if (Random().NextBoolean())
+                    {
+                        fastMatchFilter = NumericRangeFilter.NewDoubleRange("field", minValue, maxValue, true, true);
+                    }
+                    else
+                    {
+                        fastMatchFilter = NumericRangeFilter.NewDoubleRange("field", minAcceptedValue, maxAcceptedValue, true, true);
+                    }
+                }
+                else
+                {
+                    fastMatchFilter = null;
+                }
+                ValueSource vs = new DoubleFieldSource("field");
+                Facets facets = new DoubleRangeFacetCounts("field", vs, sfc, fastMatchFilter, ranges);
+                FacetResult result = facets.GetTopChildren(10, "field");
+                Assert.AreEqual(numRange, result.labelValues.Length);
+                for (int rangeID = 0; rangeID < numRange; rangeID++)
+                {
+                    if (VERBOSE)
+                    {
+                        Console.WriteLine("  range " + rangeID + " expectedCount=" + expectedCounts[rangeID]);
+                    }
+                    LabelAndValue subNode = result.labelValues[rangeID];
+                    Assert.AreEqual("r" + rangeID, subNode.label);
+                    Assert.AreEqual(expectedCounts[rangeID], (int)subNode.value);
+
+                    DoubleRange range = ranges[rangeID];
+
+                    // Test drill-down:
+                    DrillDownQuery ddq = new DrillDownQuery(config);
+                    if (Random().NextBoolean())
+                    {
+                        if (Random().NextBoolean())
+                        {
+                            ddq.Add("field", NumericRangeFilter.NewDoubleRange("field", range.min, range.max, range.minInclusive, range.maxInclusive));
+                        }
+                        else
+                        {
+                            ddq.Add("field", NumericRangeQuery.NewDoubleRange("field", range.min, range.max, range.minInclusive, range.maxInclusive));
+                        }
+                    }
+                    else
+                    {
+                        ddq.Add("field", range.GetFilter(fastMatchFilter, vs));
+                    }
+
+                    Assert.AreEqual(expectedCounts[rangeID], s.Search(ddq, 10).TotalHits);
+                }
+            }
+
+            IOUtils.Close(w, r, dir);
+        }
+
+        // LUCENE-5178
+        [Test]
+        public virtual void TestMissingValues()
+        {
+            AssumeTrue("codec does not support docsWithField", DefaultCodecSupportsDocsWithField());
+            Directory d = NewDirectory();
+            RandomIndexWriter w = new RandomIndexWriter(Random(), d);
+            Document doc = new Document();
+            NumericDocValuesField field = new NumericDocValuesField("field", 0L);
+            doc.Add(field);
+            for (long l = 0; l < 100; l++)
+            {
+                if (l % 5 == 0)
+                {
+                    // Every 5th doc is missing the value:
+                    w.AddDocument(new Document());
+                    continue;
+                }
+                field.LongValue = l;
+                w.AddDocument(doc);
+            }
+
+            IndexReader r = w.Reader;
+
+            FacetsCollector fc = new FacetsCollector();
+
+            IndexSearcher s = NewSearcher(r);
+            s.Search(new MatchAllDocsQuery(), fc);
+            Facets facets = new LongRangeFacetCounts("field", fc, new LongRange("less than 10", 0L, true, 10L, false), new LongRange("less than or equal to 10", 0L, true, 10L, true), new LongRange("over 90", 90L, false, 100L, false), new LongRange("90 or above", 90L, true, 100L, false), new LongRange("over 1000", 1000L, false, long.MaxValue, false));
+
+            Assert.AreEqual("dim=field path=[] value=16 childCount=5\n  less than 10 (8)\n  less than or equal to 10 (8)\n  over 90 (8)\n  90 or above (8)\n  over 1000 (0)\n", facets.GetTopChildren(10, "field").ToString());
+
+            IOUtils.Close(w, r, d);
+        }
+
+        [Test]
+        public virtual void TestCustomDoublesValueSource()
+        {
+            Directory dir = NewDirectory();
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir);
+
+            Document doc = new Document();
+            writer.AddDocument(doc);
+            writer.AddDocument(doc);
+            writer.AddDocument(doc);
+
+            // Test wants 3 docs in one segment:
+            writer.ForceMerge(1);
+
+            var vs = new ValueSourceAnonymousInnerClassHelper(this, doc);
+
+            FacetsConfig config = new FacetsConfig();
+
+            FacetsCollector fc = new FacetsCollector();
+
+            IndexReader r = writer.Reader;
+            IndexSearcher s = NewSearcher(r);
+            s.Search(new MatchAllDocsQuery(), fc);
+
+            DoubleRange[] ranges = new DoubleRange[] { new DoubleRange("< 1", 0.0, true, 1.0, false), new DoubleRange("< 2", 0.0, true, 2.0, false), new DoubleRange("< 5", 0.0, true, 5.0, false), new DoubleRange("< 10", 0.0, true, 10.0, false), new DoubleRange("< 20", 0.0, true, 20.0, false), new DoubleRange("< 50", 0.0, true, 50.0, false) };
+
+            Filter fastMatchFilter;
+            AtomicBoolean filterWasUsed = new AtomicBoolean();
+            if (Random().NextBoolean())
+            {
+                // Sort of silly:
+                fastMatchFilter = new CachingWrapperFilterAnonymousInnerClassHelper(this, new QueryWrapperFilter(new MatchAllDocsQuery()), filterWasUsed);
+            }
+            else
+            {
+                fastMatchFilter = null;
+            }
+
+            if (VERBOSE)
+            {
+                Console.WriteLine("TEST: fastMatchFilter=" + fastMatchFilter);
+            }
+
+            Facets facets = new DoubleRangeFacetCounts("field", vs, fc, fastMatchFilter, ranges);
+
+            Assert.AreEqual("dim=field path=[] value=3 childCount=6\n  < 1 (0)\n  < 2 (1)\n  < 5 (3)\n  < 10 (3)\n  < 20 (3)\n  < 50 (3)\n", facets.GetTopChildren(10, "field").ToString());
+            Assert.True(fastMatchFilter == null || filterWasUsed.Get());
+
+            DrillDownQuery ddq = new DrillDownQuery(config);
+            ddq.Add("field", ranges[1].GetFilter(fastMatchFilter, vs));
+
+            // Test simple drill-down:
+            Assert.AreEqual(1, s.Search(ddq, 10).TotalHits);
+
+            // Test drill-sideways after drill-down
+            DrillSideways ds = new DrillSidewaysAnonymousInnerClassHelper2(this, s, config, (TaxonomyReader)null, vs, ranges, fastMatchFilter);
+
+
+            DrillSidewaysResult dsr = ds.Search(ddq, 10);
+            Assert.AreEqual(1, dsr.Hits.TotalHits);
+            Assert.AreEqual("dim=field path=[] value=3 childCount=6\n  < 1 (0)\n  < 2 (1)\n  < 5 (3)\n  < 10 (3)\n  < 20 (3)\n  < 50 (3)\n", dsr.Facets.GetTopChildren(10, "field").ToString());
+
+            IOUtils.Close(r, writer, dir);
+        }
+
+        private class ValueSourceAnonymousInnerClassHelper : ValueSource
+        {
+            private readonly TestRangeFacetCounts outerInstance;
+
+            private Document doc;
+
+            public ValueSourceAnonymousInnerClassHelper(TestRangeFacetCounts outerInstance, Document doc)
+            {
+                this.outerInstance = outerInstance;
+                this.doc = doc;
+            }
+
+            public override FunctionValues GetValues(IDictionary ignored, AtomicReaderContext ignored2)
+            {
+                return new DoubleDocValuesAnonymousInnerClassHelper(this);
+            }
+
+            private class DoubleDocValuesAnonymousInnerClassHelper : DoubleDocValues
+            {
+                private readonly ValueSourceAnonymousInnerClassHelper outerInstance;
+
+                public DoubleDocValuesAnonymousInnerClassHelper(ValueSourceAnonymousInnerClassHelper outerInstance)
+                    : base(null)
+                {
+                    this.outerInstance = outerInstance;
+                }
+
+                public override double DoubleVal(int doc)
+                {
+                    return doc + 1;
+                }
+            }
+
+            public override bool Equals(object o)
+            {
+                throw new System.NotSupportedException();
+            }
+
+            public override int GetHashCode()
+            {
+                throw new System.NotSupportedException();
+            }
+
+            public override string Description
+            {
+                get { throw new NotSupportedException(); }
+            }
+
+        }
+
+        private class CachingWrapperFilterAnonymousInnerClassHelper : CachingWrapperFilter
+        {
+            private readonly TestRangeFacetCounts outerInstance;
+
+            private AtomicBoolean filterWasUsed;
+
+            public CachingWrapperFilterAnonymousInnerClassHelper(TestRangeFacetCounts outerInstance, QueryWrapperFilter org, AtomicBoolean filterWasUsed)
+                : base(org)
+            {
+                this.outerInstance = outerInstance;
+                this.filterWasUsed = filterWasUsed;
+            }
+
+            protected override DocIdSet CacheImpl(DocIdSetIterator iterator, AtomicReader reader)
+            {
+                var cached = new FixedBitSet(reader.MaxDoc);
+                filterWasUsed.Set(true);
+                cached.Or(iterator);
+                return cached;
+            }
+        }
+
+        private class DrillSidewaysAnonymousInnerClassHelper2 : DrillSideways
+        {
+            private readonly TestRangeFacetCounts outerInstance;
+
+            private ValueSource vs;
+            private Lucene.Net.Facet.Range.DoubleRange[] ranges;
+            private Filter fastMatchFilter;
+
+
+            public DrillSidewaysAnonymousInnerClassHelper2(TestRangeFacetCounts testRangeFacetCounts, IndexSearcher indexSearcher, FacetsConfig facetsConfig, TaxonomyReader org, ValueSource valueSource, DoubleRange[] doubleRanges, Filter filter)
+                : base(indexSearcher, facetsConfig, org)
+            {
+                this.outerInstance = outerInstance;
+                this.vs = vs;
+                this.ranges = ranges;
+                this.fastMatchFilter = fastMatchFilter;
+            }
+
+
+            protected override Facets BuildFacetsResult(FacetsCollector drillDowns, FacetsCollector[] drillSideways, string[] drillSidewaysDims)
+            {
+                Debug.Assert(drillSideways.Length == 1);
+                return new DoubleRangeFacetCounts("field", vs, drillSideways[0], fastMatchFilter, ranges);
+            }
+
+            protected override bool ScoreSubDocsAtOnce()
+            {
+                return Random().NextBoolean();
+            }
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/SlowRAMDirectory.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/SlowRAMDirectory.cs b/src/Lucene.Net.Tests/core/Facet/SlowRAMDirectory.cs
new file mode 100644
index 0000000..72de557
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/SlowRAMDirectory.cs
@@ -0,0 +1,261 @@
+using System;
+using System.Threading;
+using Lucene.Net.Randomized.Generators;
+
+namespace Lucene.Net.Facet
+{
+
+    /*
+     * 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 IOContext = Lucene.Net.Store.IOContext;
+    using IndexInput = Lucene.Net.Store.IndexInput;
+    using IndexOutput = Lucene.Net.Store.IndexOutput;
+    using RAMDirectory = Lucene.Net.Store.RAMDirectory;
+    using ThreadInterruptedException = Lucene.Net.Util.ThreadInterruptedException;
+
+    /// <summary>
+    /// Test utility - slow directory
+    /// </summary>
+    // TODO: move to test-framework and sometimes use in tests?
+    public class SlowRAMDirectory : RAMDirectory
+    {
+
+        private const int IO_SLEEP_THRESHOLD = 50;
+
+        internal Random random;
+        private int sleepMillis;
+
+        public virtual int SleepMillis
+        {
+            set
+            {
+                this.sleepMillis = value;
+            }
+        }
+
+        public SlowRAMDirectory(int sleepMillis, Random random)
+        {
+            this.sleepMillis = sleepMillis;
+            this.random = random;
+        }
+
+        public override IndexOutput CreateOutput(string name, IOContext context)
+        {
+            if (sleepMillis != -1)
+            {
+                return new SlowIndexOutput(this, base.CreateOutput(name, context));
+            }
+
+            return base.CreateOutput(name, context);
+        }
+
+        public override IndexInput OpenInput(string name, IOContext context)
+        {
+            if (sleepMillis != -1)
+            {
+                return new SlowIndexInput(this, base.OpenInput(name, context));
+            }
+            return base.OpenInput(name, context);
+        }
+
+        internal virtual void doSleep(Random random, int length)
+        {
+            int sTime = length < 10 ? sleepMillis : (int)(sleepMillis * Math.Log(length));
+            if (random != null)
+            {
+                sTime = random.Next(sTime);
+            }
+            try
+            {
+                Thread.Sleep(sTime);
+            }
+            catch (ThreadInterruptedException e)
+            {
+                throw new ThreadInterruptedException(e);
+            }
+        }
+
+        /// <summary>
+        /// Make a private random. </summary>
+        internal virtual Random forkRandom()
+        {
+            if (random == null)
+            {
+                return null;
+            }
+            return new Random((int)random.NextLong());
+        }
+
+        /// <summary>
+        /// Delegate class to wrap an IndexInput and delay reading bytes by some
+        /// specified time.
+        /// </summary>
+        private class SlowIndexInput : IndexInput
+        {
+            private readonly SlowRAMDirectory outerInstance;
+
+            internal IndexInput ii;
+            internal int numRead = 0;
+            internal Random rand;
+
+            public SlowIndexInput(SlowRAMDirectory outerInstance, IndexInput ii)
+                : base("SlowIndexInput(" + ii + ")")
+            {
+                this.outerInstance = outerInstance;
+                this.rand = outerInstance.forkRandom();
+                this.ii = ii;
+            }
+
+            public override byte ReadByte()
+            {
+                if (numRead >= IO_SLEEP_THRESHOLD)
+                {
+                    outerInstance.doSleep(rand, 0);
+                    numRead = 0;
+                }
+                ++numRead;
+                return ii.ReadByte();
+            }
+
+            public override void ReadBytes(byte[] b, int offset, int len)
+            {
+                if (numRead >= IO_SLEEP_THRESHOLD)
+                {
+                    outerInstance.doSleep(rand, len);
+                    numRead = 0;
+                }
+                numRead += len;
+                ii.ReadBytes(b, offset, len);
+            }
+
+
+            // TODO: is it intentional that clone doesnt wrap?
+            public override object Clone()
+            {
+                return ii.Clone();
+            }
+
+
+            public override void Dispose()
+            {
+                ii.Dispose();
+            }
+            public override bool Equals(object o)
+            {
+                return ii.Equals(o);
+            }
+            public override long FilePointer
+            {
+                get
+                {
+                    return ii.FilePointer;
+                }
+            }
+
+            public override void Seek(long pos)
+            {
+                ii.Seek(pos);
+            }
+
+
+            public override int GetHashCode()
+            {
+                return ii.GetHashCode();
+            }
+            public override long Length()
+            {
+                return ii.Length();
+            }
+
+        }
+
+        /// <summary>
+        /// Delegate class to wrap an IndexOutput and delay writing bytes by some
+        /// specified time.
+        /// </summary>
+        private class SlowIndexOutput : IndexOutput
+        {
+            private readonly SlowRAMDirectory outerInstance;
+
+
+            internal IndexOutput io;
+            internal int numWrote;
+            internal readonly Random rand;
+
+            public SlowIndexOutput(SlowRAMDirectory outerInstance, IndexOutput io)
+            {
+                this.outerInstance = outerInstance;
+                this.io = io;
+                this.rand = outerInstance.forkRandom();
+            }
+
+            public override void WriteByte(byte b)
+            {
+                if (numWrote >= IO_SLEEP_THRESHOLD)
+                {
+                    outerInstance.doSleep(rand, 0);
+                    numWrote = 0;
+                }
+                ++numWrote;
+                io.WriteByte(b);
+            }
+
+            public override void WriteBytes(byte[] b, int offset, int length)
+            {
+                if (numWrote >= IO_SLEEP_THRESHOLD)
+                {
+                    outerInstance.doSleep(rand, length);
+                    numWrote = 0;
+                }
+                numWrote += length;
+                io.WriteBytes(b, offset, length);
+            }
+
+            public override void Dispose()
+            {
+                io.Dispose();
+            }
+            public override void Flush()
+            {
+                io.Flush();
+            }
+            public override long FilePointer
+            {
+                get
+                {
+                    return io.FilePointer;
+                }
+            }
+            public override void Seek(long pos)
+            {
+                io.Seek(pos);
+            }
+
+            public override long Checksum
+            {
+                get
+                {
+                    return io.Checksum;
+                }
+            }
+        }
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/SortedSet/TestSortedSetDocValuesFacets.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/SortedSet/TestSortedSetDocValuesFacets.cs b/src/Lucene.Net.Tests/core/Facet/SortedSet/TestSortedSetDocValuesFacets.cs
new file mode 100644
index 0000000..091a2c8
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/SortedSet/TestSortedSetDocValuesFacets.cs
@@ -0,0 +1,394 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using Lucene.Net.Randomized;
+using Lucene.Net.Randomized.Generators;
+using NUnit.Framework;
+
+namespace Lucene.Net.Facet.SortedSet
+{
+
+    /*
+     * 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 Document = Lucene.Net.Documents.Document;
+    using Field = Lucene.Net.Documents.Field;
+    using IndexReader = Lucene.Net.Index.IndexReader;
+    using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter;
+    using SlowCompositeReaderWrapper = Lucene.Net.Index.SlowCompositeReaderWrapper;
+    using Term = Lucene.Net.Index.Term;
+    using IndexSearcher = Lucene.Net.Search.IndexSearcher;
+    using MatchAllDocsQuery = Lucene.Net.Search.MatchAllDocsQuery;
+    using TermQuery = Lucene.Net.Search.TermQuery;
+    using TopDocs = Lucene.Net.Search.TopDocs;
+    using Directory = Lucene.Net.Store.Directory;
+    using IOUtils = Lucene.Net.Util.IOUtils;
+    using TestUtil = Lucene.Net.Util.TestUtil;
+    
+    [TestFixture]
+    public class TestSortedSetDocValuesFacets : FacetTestCase
+    {
+
+        // NOTE: TestDrillSideways.testRandom also sometimes
+        // randomly uses SortedSetDV
+        [Test]
+        public virtual void TestBasic()
+        {
+
+            AssumeTrue("Test requires SortedSetDV support", DefaultCodecSupportsSortedSet());
+            Directory dir = NewDirectory();
+
+            FacetsConfig config = new FacetsConfig();
+            config.SetMultiValued("a", true);
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir);
+
+            Document doc = new Document();
+            doc.Add(new SortedSetDocValuesFacetField("a", "foo"));
+            doc.Add(new SortedSetDocValuesFacetField("a", "bar"));
+            doc.Add(new SortedSetDocValuesFacetField("a", "zoo"));
+            doc.Add(new SortedSetDocValuesFacetField("b", "baz"));
+            writer.AddDocument(config.Build(doc));
+            if (Random().NextBoolean())
+            {
+                writer.Commit();
+            }
+
+            doc = new Document();
+            doc.Add(new SortedSetDocValuesFacetField("a", "foo"));
+            writer.AddDocument(config.Build(doc));
+
+            // NRT open
+            IndexSearcher searcher = NewSearcher(writer.Reader);
+
+            // Per-top-reader state:
+            SortedSetDocValuesReaderState state = new DefaultSortedSetDocValuesReaderState(searcher.IndexReader);
+
+            FacetsCollector c = new FacetsCollector();
+
+            searcher.Search(new MatchAllDocsQuery(), c);
+
+            SortedSetDocValuesFacetCounts facets = new SortedSetDocValuesFacetCounts(state, c);
+
+            Assert.AreEqual("dim=a path=[] value=4 childCount=3\n  foo (2)\n  bar (1)\n  zoo (1)\n", facets.GetTopChildren(10, "a").ToString());
+            Assert.AreEqual("dim=b path=[] value=1 childCount=1\n  baz (1)\n", facets.GetTopChildren(10, "b").ToString());
+
+            // DrillDown:
+            DrillDownQuery q = new DrillDownQuery(config);
+            q.Add("a", "foo");
+            q.Add("b", "baz");
+            TopDocs hits = searcher.Search(q, 1);
+            Assert.AreEqual(1, hits.TotalHits);
+
+            IOUtils.Close(writer, searcher.IndexReader, dir);
+        }
+
+        // LUCENE-5090
+        [Test]
+        public virtual void TestStaleState()
+        {
+            AssumeTrue("Test requires SortedSetDV support", DefaultCodecSupportsSortedSet());
+            Directory dir = NewDirectory();
+
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir);
+
+            FacetsConfig config = new FacetsConfig();
+
+            Document doc = new Document();
+            doc.Add(new SortedSetDocValuesFacetField("a", "foo"));
+            writer.AddDocument(config.Build(doc));
+
+            IndexReader r = writer.Reader;
+            SortedSetDocValuesReaderState state = new DefaultSortedSetDocValuesReaderState(r);
+
+            doc = new Document();
+            doc.Add(new SortedSetDocValuesFacetField("a", "bar"));
+            writer.AddDocument(config.Build(doc));
+
+            doc = new Document();
+            doc.Add(new SortedSetDocValuesFacetField("a", "baz"));
+            writer.AddDocument(config.Build(doc));
+
+            IndexSearcher searcher = NewSearcher(writer.Reader);
+
+            FacetsCollector c = new FacetsCollector();
+
+            searcher.Search(new MatchAllDocsQuery(), c);
+
+            try
+            {
+                new SortedSetDocValuesFacetCounts(state, c);
+                Fail("did not hit expected exception");
+            }
+            catch (IllegalStateException)
+            {
+                // expected
+            }
+
+            r.Dispose();
+            writer.Dispose();
+            searcher.IndexReader.Dispose();
+            dir.Dispose();
+        }
+
+        // LUCENE-5333
+        [Test]
+        public virtual void TestSparseFacets()
+        {
+            AssumeTrue("Test requires SortedSetDV support", DefaultCodecSupportsSortedSet());
+            Directory dir = NewDirectory();
+
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir);
+
+            FacetsConfig config = new FacetsConfig();
+
+            Document doc = new Document();
+            doc.Add(new SortedSetDocValuesFacetField("a", "foo1"));
+            writer.AddDocument(config.Build(doc));
+
+            if (Random().NextBoolean())
+            {
+                writer.Commit();
+            }
+
+            doc = new Document();
+            doc.Add(new SortedSetDocValuesFacetField("a", "foo2"));
+            doc.Add(new SortedSetDocValuesFacetField("b", "bar1"));
+            writer.AddDocument(config.Build(doc));
+
+            if (Random().NextBoolean())
+            {
+                writer.Commit();
+            }
+
+            doc = new Document();
+            doc.Add(new SortedSetDocValuesFacetField("a", "foo3"));
+            doc.Add(new SortedSetDocValuesFacetField("b", "bar2"));
+            doc.Add(new SortedSetDocValuesFacetField("c", "baz1"));
+            writer.AddDocument(config.Build(doc));
+
+            // NRT open
+            IndexSearcher searcher = NewSearcher(writer.Reader);
+            writer.Dispose();
+
+            // Per-top-reader state:
+            SortedSetDocValuesReaderState state = new DefaultSortedSetDocValuesReaderState(searcher.IndexReader);
+
+            FacetsCollector c = new FacetsCollector();
+            searcher.Search(new MatchAllDocsQuery(), c);
+            SortedSetDocValuesFacetCounts facets = new SortedSetDocValuesFacetCounts(state, c);
+
+            // Ask for top 10 labels for any dims that have counts:
+            IList<FacetResult> results = facets.GetAllDims(10);
+
+            Assert.AreEqual(3, results.Count);
+            Assert.AreEqual("dim=a path=[] value=3 childCount=3\n  foo1 (1)\n  foo2 (1)\n  foo3 (1)\n", results[0].ToString());
+            Assert.AreEqual("dim=b path=[] value=2 childCount=2\n  bar1 (1)\n  bar2 (1)\n", results[1].ToString());
+            Assert.AreEqual("dim=c path=[] value=1 childCount=1\n  baz1 (1)\n", results[2].ToString());
+
+            searcher.IndexReader.Dispose();
+            dir.Dispose();
+        }
+
+        [Test]
+        public virtual void TestSomeSegmentsMissing()
+        {
+            AssumeTrue("Test requires SortedSetDV support", DefaultCodecSupportsSortedSet());
+            Directory dir = NewDirectory();
+
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir);
+
+            FacetsConfig config = new FacetsConfig();
+
+            Document doc = new Document();
+            doc.Add(new SortedSetDocValuesFacetField("a", "foo1"));
+            writer.AddDocument(config.Build(doc));
+            writer.Commit();
+
+            doc = new Document();
+            writer.AddDocument(config.Build(doc));
+            writer.Commit();
+
+            doc = new Document();
+            doc.Add(new SortedSetDocValuesFacetField("a", "foo2"));
+            writer.AddDocument(config.Build(doc));
+            writer.Commit();
+
+            // NRT open
+            IndexSearcher searcher = NewSearcher(writer.Reader);
+            writer.Dispose();
+
+            // Per-top-reader state:
+            SortedSetDocValuesReaderState state = new DefaultSortedSetDocValuesReaderState(searcher.IndexReader);
+
+            FacetsCollector c = new FacetsCollector();
+            searcher.Search(new MatchAllDocsQuery(), c);
+            SortedSetDocValuesFacetCounts facets = new SortedSetDocValuesFacetCounts(state, c);
+
+            // Ask for top 10 labels for any dims that have counts:
+            Assert.AreEqual("dim=a path=[] value=2 childCount=2\n  foo1 (1)\n  foo2 (1)\n", facets.GetTopChildren(10, "a").ToString());
+
+            searcher.IndexReader.Dispose();
+            dir.Dispose();
+        }
+
+        [Test]
+        public virtual void TestSlowCompositeReaderWrapper()
+        {
+            AssumeTrue("Test requires SortedSetDV support", DefaultCodecSupportsSortedSet());
+            Directory dir = NewDirectory();
+
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir);
+
+            FacetsConfig config = new FacetsConfig();
+
+            Document doc = new Document();
+            doc.Add(new SortedSetDocValuesFacetField("a", "foo1"));
+            writer.AddDocument(config.Build(doc));
+
+            writer.Commit();
+
+            doc = new Document();
+            doc.Add(new SortedSetDocValuesFacetField("a", "foo2"));
+            writer.AddDocument(config.Build(doc));
+
+            // NRT open
+            IndexSearcher searcher = new IndexSearcher(SlowCompositeReaderWrapper.Wrap(writer.Reader));
+
+            // Per-top-reader state:
+            SortedSetDocValuesReaderState state = new DefaultSortedSetDocValuesReaderState(searcher.IndexReader);
+
+            FacetsCollector c = new FacetsCollector();
+            searcher.Search(new MatchAllDocsQuery(), c);
+            Facets facets = new SortedSetDocValuesFacetCounts(state, c);
+
+            // Ask for top 10 labels for any dims that have counts:
+            Assert.AreEqual("dim=a path=[] value=2 childCount=2\n  foo1 (1)\n  foo2 (1)\n", facets.GetTopChildren(10, "a").ToString());
+
+            IOUtils.Close(writer, searcher.IndexReader, dir);
+        }
+
+
+        [Test]
+        public virtual void TestRandom()
+        {
+            AssumeTrue("Test requires SortedSetDV support", DefaultCodecSupportsSortedSet());
+            string[] tokens = GetRandomTokens(10);
+            Directory indexDir = NewDirectory();
+            Directory taxoDir = NewDirectory();
+
+            RandomIndexWriter w = new RandomIndexWriter(Random(), indexDir);
+            FacetsConfig config = new FacetsConfig();
+            int numDocs = AtLeast(1000);
+            int numDims = TestUtil.NextInt(Random(), 1, 7);
+            IList<TestDoc> testDocs = GetRandomDocs(tokens, numDocs, numDims);
+            foreach (TestDoc testDoc in testDocs)
+            {
+                Document doc = new Document();
+                doc.Add(NewStringField("content", testDoc.content, Field.Store.NO));
+                for (int j = 0; j < numDims; j++)
+                {
+                    if (testDoc.dims[j] != null)
+                    {
+                        doc.Add(new SortedSetDocValuesFacetField("dim" + j, testDoc.dims[j]));
+                    }
+                }
+                w.AddDocument(config.Build(doc));
+            }
+
+            // NRT open
+            IndexSearcher searcher = NewSearcher(w.Reader);
+
+            // Per-top-reader state:
+            SortedSetDocValuesReaderState state = new DefaultSortedSetDocValuesReaderState(searcher.IndexReader);
+
+            int iters = AtLeast(100);
+            for (int iter = 0; iter < iters; iter++)
+            {
+                string searchToken = tokens[Random().Next(tokens.Length)];
+                if (VERBOSE)
+                {
+                    Console.WriteLine("\nTEST: iter content=" + searchToken);
+                }
+                FacetsCollector fc = new FacetsCollector();
+                FacetsCollector.Search(searcher, new TermQuery(new Term("content", searchToken)), 10, fc);
+                Facets facets = new SortedSetDocValuesFacetCounts(state, fc);
+
+                // Slow, yet hopefully bug-free, faceting:
+                var expectedCounts = new List<Dictionary<string, int?>>();
+                for (int i = 0; i < numDims; i++)
+                {
+                    expectedCounts.Add(new Dictionary<string, int?>());
+                }
+
+                foreach (TestDoc doc in testDocs)
+                {
+                    if (doc.content.Equals(searchToken))
+                    {
+                        for (int j = 0; j < numDims; j++)
+                        {
+                            if (doc.dims[j] != null)
+                            {
+                                int? v; 
+                                
+                                if (!expectedCounts[j].TryGetValue(doc.dims[j],out v))
+                                {
+                                    expectedCounts[j][doc.dims[j]] = 1;
+                                }
+                                else
+                                {
+                                    expectedCounts[j][doc.dims[j]] = (int)v + 1;
+                                }
+                            }
+                        }
+                    }
+                }
+
+                IList<FacetResult> expected = new List<FacetResult>();
+                for (int i = 0; i < numDims; i++)
+                {
+                    IList<LabelAndValue> labelValues = new List<LabelAndValue>();
+                    int totCount = 0;
+                    foreach (KeyValuePair<string, int?> ent in expectedCounts[i])
+                    {
+                        labelValues.Add(new LabelAndValue(ent.Key, ent.Value.Value));
+                        totCount += ent.Value.Value;
+                    }
+                    SortLabelValues(labelValues);
+                    if (totCount > 0)
+                    {
+                        expected.Add(new FacetResult("dim" + i, new string[0], totCount, labelValues.ToArray(), labelValues.Count));
+                    }
+                }
+
+                // Sort by highest value, tie break by value:
+                SortFacetResults(expected);
+
+                IList<FacetResult> actual = facets.GetAllDims(10);
+
+                // Messy: fixup ties
+                //sortTies(actual);
+
+                Assert.AreEqual(expected, actual);
+            }
+
+            IOUtils.Close(w, searcher.IndexReader, indexDir, taxoDir);
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestAddTaxonomy.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestAddTaxonomy.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestAddTaxonomy.cs
new file mode 100644
index 0000000..2ece8ca
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestAddTaxonomy.cs
@@ -0,0 +1,323 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Lucene.Net.Randomized.Generators;
+using Lucene.Net.Support;
+using NUnit.Framework;
+
+namespace Lucene.Net.Facet.Taxonomy.Directory
+{
+
+
+    using DiskOrdinalMap = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter.DiskOrdinalMap;
+    using MemoryOrdinalMap = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter.MemoryOrdinalMap;
+    using OrdinalMap = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter.OrdinalMap;
+    using Directory = Lucene.Net.Store.Directory;
+    using IOUtils = Lucene.Net.Util.IOUtils;
+    using TestUtil = Lucene.Net.Util.TestUtil;
+
+    /*
+     * 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.
+     */
+    [TestFixture]
+    public class TestAddTaxonomy : FacetTestCase
+    {
+        private void Dotest(int ncats, int range)
+        {
+            AtomicInteger numCats = new AtomicInteger(ncats);
+            Directory[] dirs = new Directory[2];
+            for (int i = 0; i < dirs.Length; i++)
+            {
+                dirs[i] = NewDirectory();
+                var tw = new DirectoryTaxonomyWriter(dirs[i]);
+                ThreadClass[] addThreads = new ThreadClass[4];
+                for (int j = 0; j < addThreads.Length; j++)
+                {
+                    addThreads[j] = new ThreadAnonymousInnerClassHelper(this, range, numCats, tw);
+                }
+
+                foreach (ThreadClass t in addThreads)
+                {
+                    t.Start();
+                }
+                foreach (ThreadClass t in addThreads)
+                {
+                    t.Join();
+                }
+
+                tw.Dispose();
+            }
+
+            var tw1 = new DirectoryTaxonomyWriter(dirs[0]);
+            OrdinalMap map = randomOrdinalMap();
+            tw1.AddTaxonomy(dirs[1], map);
+            tw1.Dispose();
+
+            validate(dirs[0], dirs[1], map);
+
+            IOUtils.Close(dirs);
+        }
+
+        private class ThreadAnonymousInnerClassHelper : ThreadClass
+        {
+            private readonly TestAddTaxonomy outerInstance;
+
+            private int range;
+            private AtomicInteger numCats;
+            private DirectoryTaxonomyWriter tw;
+
+            public ThreadAnonymousInnerClassHelper(TestAddTaxonomy outerInstance, int range, AtomicInteger numCats, DirectoryTaxonomyWriter tw)
+            {
+                this.outerInstance = outerInstance;
+                this.range = range;
+                this.numCats = numCats;
+                this.tw = tw;
+            }
+
+            public override void Run()
+            {
+                Random random = Random();
+                while (numCats.DecrementAndGet() > 0)
+                {
+                    string cat = Convert.ToString(random.Next(range));
+                    try
+                    {
+                        tw.AddCategory(new FacetLabel("a", cat));
+                    }
+                    catch (IOException e)
+                    {
+                        throw new Exception(e.Message, e);
+                    }
+                }
+            }
+        }
+
+
+        private OrdinalMap randomOrdinalMap()
+        {
+            if (Random().NextBoolean())
+            {
+                return new DiskOrdinalMap("taxoMap");
+            }
+            else
+            {
+                return new MemoryOrdinalMap();
+            }
+        }
+
+        private void validate(Directory dest, Directory src, OrdinalMap ordMap)
+        {
+            var destTr = new DirectoryTaxonomyReader(dest);
+            try
+            {
+                int destSize = destTr.Size;
+                var srcTR = new DirectoryTaxonomyReader(src);
+                try
+                {
+                    var map = ordMap.Map;
+
+                    // validate taxo sizes
+                    int srcSize = srcTR.Size;
+                    Assert.True(destSize >= srcSize, "destination taxonomy expected to be larger than source; dest=" + destSize + " src=" + srcSize);
+
+                    // validate that all source categories exist in destination, and their
+                    // ordinals are as expected.
+                    for (int j = 1; j < srcSize; j++)
+                    {
+                        FacetLabel cp = srcTR.GetPath(j);
+                        int destOrdinal = destTr.GetOrdinal(cp);
+                        Assert.True(destOrdinal > 0, cp + " not found in destination");
+                        Assert.AreEqual(destOrdinal, map[j]);
+                    }
+                }
+                finally
+                {
+                    ((TaxonomyReader)srcTR).Dispose(true);
+                }
+            }
+            finally
+            {
+                ((TaxonomyReader)destTr).Dispose(true);
+            }
+        }
+
+        [Test]
+        public virtual void TestAddEmpty()
+        {
+            Directory dest = NewDirectory();
+            var destTW = new DirectoryTaxonomyWriter(dest);
+            destTW.AddCategory(new FacetLabel("Author", "Rob Pike"));
+            destTW.AddCategory(new FacetLabel("Aardvarks", "Bob"));
+            destTW.Commit();
+
+            Directory src = NewDirectory();
+            (new DirectoryTaxonomyWriter(src)).Dispose(); // create an empty taxonomy
+
+            OrdinalMap map = randomOrdinalMap();
+            destTW.AddTaxonomy(src, map);
+            destTW.Dispose();
+
+            validate(dest, src, map);
+
+            IOUtils.Close(dest, src);
+        }
+
+        [Test]
+        public virtual void TestAddToEmpty()
+        {
+            Directory dest = NewDirectory();
+
+            Directory src = NewDirectory();
+            DirectoryTaxonomyWriter srcTW = new DirectoryTaxonomyWriter(src);
+            srcTW.AddCategory(new FacetLabel("Author", "Rob Pike"));
+            srcTW.AddCategory(new FacetLabel("Aardvarks", "Bob"));
+            srcTW.Dispose();
+
+            DirectoryTaxonomyWriter destTW = new DirectoryTaxonomyWriter(dest);
+            OrdinalMap map = randomOrdinalMap();
+            destTW.AddTaxonomy(src, map);
+            destTW.Dispose();
+
+            validate(dest, src, map);
+
+            IOUtils.Close(dest, src);
+        }
+
+        // A more comprehensive and big random test.
+        [Test]
+        public virtual void TestBig()
+        {
+            Dotest(200, 10000);
+            Dotest(1000, 20000);
+            Dotest(400000, 1000000);
+        }
+
+        // a reasonable random test
+        [Test]
+        public virtual void TestMedium()
+        {
+            Random random = Random();
+            int numTests = AtLeast(3);
+            for (int i = 0; i < numTests; i++)
+            {
+                Dotest(TestUtil.NextInt(random, 2, 100), TestUtil.NextInt(random, 100, 1000));
+            }
+        }
+
+        [Test]
+        public virtual void TestSimple()
+        {
+            Directory dest = NewDirectory();
+            var tw1 = new DirectoryTaxonomyWriter(dest);
+            tw1.AddCategory(new FacetLabel("Author", "Mark Twain"));
+            tw1.AddCategory(new FacetLabel("Animals", "Dog"));
+            tw1.AddCategory(new FacetLabel("Author", "Rob Pike"));
+
+            Directory src = NewDirectory();
+            var tw2 = new DirectoryTaxonomyWriter(src);
+            tw2.AddCategory(new FacetLabel("Author", "Rob Pike"));
+            tw2.AddCategory(new FacetLabel("Aardvarks", "Bob"));
+            tw2.Dispose();
+
+            OrdinalMap map = randomOrdinalMap();
+
+            tw1.AddTaxonomy(src, map);
+            tw1.Dispose();
+
+            validate(dest, src, map);
+
+            IOUtils.Close(dest, src);
+        }
+
+        [Test]
+        public virtual void TestConcurrency()
+        {
+            // tests that addTaxonomy and addCategory work in parallel
+            int numCategories = AtLeast(10000);
+
+            // build an input taxonomy index
+            Directory src = NewDirectory();
+            var tw = new DirectoryTaxonomyWriter(src);
+            for (int i = 0; i < numCategories; i++)
+            {
+                tw.AddCategory(new FacetLabel("a", Convert.ToString(i)));
+            }
+            tw.Dispose();
+
+            // now add the taxonomy to an empty taxonomy, while adding the categories
+            // again, in parallel -- in the end, no duplicate categories should exist.
+            Directory dest = NewDirectory();
+            var destTW = new DirectoryTaxonomyWriter(dest);
+            ThreadClass t = new ThreadAnonymousInnerClassHelper2(this, numCategories, destTW);
+            t.Start();
+
+            OrdinalMap map = new MemoryOrdinalMap();
+            destTW.AddTaxonomy(src, map);
+            t.Join();
+            destTW.Dispose();
+
+            // now validate
+
+            var dtr = new DirectoryTaxonomyReader(dest);
+            // +2 to account for the root category + "a"
+            Assert.AreEqual(numCategories + 2, dtr.Size);
+            var categories = new HashSet<FacetLabel>();
+            for (int i = 1; i < dtr.Size; i++)
+            {
+                FacetLabel cat = dtr.GetPath(i);
+                Assert.True(categories.Add(cat), "category " + cat + " already existed");
+            }
+            (dtr).Dispose();
+
+            IOUtils.Close(src, dest);
+        }
+
+        private class ThreadAnonymousInnerClassHelper2 : ThreadClass
+        {
+            private readonly TestAddTaxonomy outerInstance;
+
+            private int numCategories;
+            private Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter destTW;
+
+            public ThreadAnonymousInnerClassHelper2(TestAddTaxonomy outerInstance, int numCategories, Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter destTW)
+            {
+                this.outerInstance = outerInstance;
+                this.numCategories = numCategories;
+                this.destTW = destTW;
+            }
+
+            public override void Run()
+            {
+                for (int i = 0; i < numCategories; i++)
+                {
+                    try
+                    {
+                        destTW.AddCategory(new FacetLabel("a", Convert.ToString(i)));
+                    }
+                    catch (IOException e)
+                    {
+                        // shouldn't happen - if it does, let the test fail on uncaught exception.
+                        throw new Exception(e.Message, e);
+                    }
+                }
+            }
+        }
+
+    }
+
+}
\ No newline at end of file


[14/26] lucenenet git commit: first commit of facet porting, failing tests will be fixed in next commits.

Posted by sy...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/FacetLabel.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/FacetLabel.cs b/Lucene.Net.Facet/Taxonomy/FacetLabel.cs
new file mode 100644
index 0000000..287a18c
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/FacetLabel.cs
@@ -0,0 +1,224 @@
+using System;
+using System.Diagnostics;
+using Lucene.Net.Support;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * 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 LruTaxonomyWriterCache = Lucene.Net.Facet.Taxonomy.WriterCache.LruTaxonomyWriterCache; 
+    using NameHashIntCacheLRU = Lucene.Net.Facet.Taxonomy.WriterCache.NameHashIntCacheLRU; 
+
+    /// <summary>
+    /// Holds a sequence of string components, specifying the hierarchical name of a
+    /// category.
+    /// 
+    /// @lucene.internal
+    /// </summary>
+    public class FacetLabel : IComparable<FacetLabel>
+    {
+        private static readonly int BYTE_BLOCK_SIZE = Lucene.Net.Util.ByteBlockPool.BYTE_BLOCK_SIZE;
+        /*
+         * copied from DocumentWriterPerThread -- if a FacetLabel is resolved to a
+         * drill-down term which is encoded to a larger term than that length, it is
+         * silently dropped! Therefore we limit the number of characters to MAX/4 to
+         * be on the safe side.
+         */
+        /// <summary>
+        /// The maximum number of characters a <seealso cref="FacetLabel"/> can have.
+        /// </summary>
+        public static readonly int MAX_CATEGORY_PATH_LENGTH = (BYTE_BLOCK_SIZE - 2) / 4;
+
+        /// <summary>
+        /// The components of this <seealso cref="FacetLabel"/>. Note that this array may be
+        /// shared with other <seealso cref="FacetLabel"/> instances, e.g. as a result of
+        /// <seealso cref="#subpath(int)"/>, therefore you should traverse the array up to
+        /// <seealso cref="#length"/> for this path's components.
+        /// </summary>
+        public readonly string[] components;
+
+        /// <summary>
+        /// The number of components of this <seealso cref="FacetLabel"/>. </summary>
+        public readonly int length;
+
+        // Used by subpath
+        private FacetLabel(FacetLabel copyFrom, int prefixLen)
+        {
+            // while the code which calls this method is safe, at some point a test
+            // tripped on AIOOBE in toString, but we failed to reproduce. adding the
+            // assert as a safety check.
+            Debug.Assert(prefixLen >= 0 && prefixLen <= copyFrom.components.Length, "prefixLen cannot be negative nor larger than the given components' length: prefixLen=" + prefixLen + " components.length=" + copyFrom.components.Length);
+            this.components = copyFrom.components;
+            length = prefixLen;
+        }
+
+        /// <summary>
+        /// Construct from the given path components. </summary>
+        public FacetLabel(params string[] components)
+        {
+            this.components = components;
+            length = components.Length;
+            CheckComponents();
+        }
+
+        /// <summary>
+        /// Construct from the dimension plus the given path components. </summary>
+        public FacetLabel(string dim, string[] path)
+        {
+            components = new string[1 + path.Length];
+            components[0] = dim;
+            Array.Copy(path, 0, components, 1, path.Length);
+            length = components.Length;
+            CheckComponents();
+        }
+
+        private void CheckComponents()
+        {
+            long len = 0;
+            foreach (string comp in components)
+            {
+                if (string.IsNullOrEmpty(comp))
+                {
+                    throw new System.ArgumentException("empty or null components not allowed: " + Arrays.ToString(components));
+                }
+                len += comp.Length;
+            }
+            len += components.Length - 1; // add separators
+            if (len > MAX_CATEGORY_PATH_LENGTH)
+            {
+                throw new System.ArgumentException("category path exceeds maximum allowed path length: max=" + MAX_CATEGORY_PATH_LENGTH + " len=" + len + " path=" + Arrays.ToString(components).Substring(0, 30) + "...");
+            }
+        }
+
+        /// <summary>
+        /// Compares this path with another <seealso cref="FacetLabel"/> for lexicographic
+        /// order.
+        /// </summary>
+        public virtual int CompareTo(FacetLabel other)
+        {
+            int len = length < other.length ? length : other.length;
+            for (int i = 0, j = 0; i < len; i++, j++)
+            {
+                int cmp = components[i].CompareTo(other.components[j]);
+                if (cmp < 0)
+                {
+                    return -1; // this is 'before'
+                }
+                if (cmp > 0)
+                {
+                    return 1; // this is 'after'
+                }
+            }
+
+            // one is a prefix of the other
+            return length - other.length;
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (!(obj is FacetLabel))
+            {
+                return false;
+            }
+
+            FacetLabel other = (FacetLabel)obj;
+            if (length != other.length)
+            {
+                return false; // not same length, cannot be equal
+            }
+
+            // CategoryPaths are more likely to differ at the last components, so start
+            // from last-first
+            for (int i = length - 1; i >= 0; i--)
+            {
+                if (!components[i].Equals(other.components[i]))
+                {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public override int GetHashCode()
+        {
+            if (length == 0)
+            {
+                return 0;
+            }
+
+            int hash = length;
+            for (int i = 0; i < length; i++)
+            {
+                hash = hash * 31 + components[i].GetHashCode();
+            }
+            return hash;
+        }
+
+        /// <summary>
+        /// Calculate a 64-bit hash function for this path.  This
+        ///  is necessary for <seealso cref="NameHashIntCacheLRU"/> (the
+        ///  default cache impl for {@link
+        ///  LruTaxonomyWriterCache}) to reduce the chance of
+        ///  "silent but deadly" collisions. 
+        /// </summary>
+        public virtual long LongHashCode()
+        {
+            if (length == 0)
+            {
+                return 0;
+            }
+
+            long hash = length;
+            for (int i = 0; i < length; i++)
+            {
+                hash = hash * 65599 + components[i].GetHashCode();
+            }
+            return hash;
+        }
+
+        /// <summary>
+        /// Returns a sub-path of this path up to {@code length} components. </summary>
+        public virtual FacetLabel Subpath(int len)
+        {
+            if (len >= this.length || len < 0)
+            {
+                return this;
+            }
+            else
+            {
+                return new FacetLabel(this, len);
+            }
+        }
+
+        /// <summary>
+        /// Returns a string representation of the path.
+        /// </summary>
+        public override string ToString()
+        {
+            if (length == 0)
+            {
+                return "FacetLabel: []";
+            }
+            string[] parts = new string[length];
+            Array.Copy(components, 0, parts, 0, length);
+            return "FacetLabel: [" + Arrays.ToString(parts) + "]";
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/FastTaxonomyFacetCounts.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/FastTaxonomyFacetCounts.cs b/Lucene.Net.Facet/Taxonomy/FastTaxonomyFacetCounts.cs
new file mode 100644
index 0000000..01545f0
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/FastTaxonomyFacetCounts.cs
@@ -0,0 +1,105 @@
+using System.Collections.Generic;
+using Lucene.Net.Facet;
+using Lucene.Net.Search;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * 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 MatchingDocs = FacetsCollector.MatchingDocs;
+    using BinaryDocValues = Lucene.Net.Index.BinaryDocValues;
+    using DocIdSetIterator = Lucene.Net.Search.DocIdSetIterator;
+    using BytesRef = Lucene.Net.Util.BytesRef;
+
+    /// <summary>
+    /// Computes facets counts, assuming the default encoding
+    ///  into DocValues was used.
+    /// 
+    /// @lucene.experimental 
+    /// </summary>
+    public class FastTaxonomyFacetCounts : IntTaxonomyFacets
+    {
+
+        /// <summary>
+        /// Create {@code FastTaxonomyFacetCounts}, which also
+        ///  counts all facet labels. 
+        /// </summary>
+        public FastTaxonomyFacetCounts(TaxonomyReader taxoReader, FacetsConfig config, FacetsCollector fc)
+            : this(FacetsConfig.DEFAULT_INDEX_FIELD_NAME, taxoReader, config, fc)
+        {
+        }
+
+        /// <summary>
+        /// Create {@code FastTaxonomyFacetCounts}, using the
+        ///  specified {@code indexFieldName} for ordinals.  Use
+        ///  this if you had set {@link
+        ///  FacetsConfig#setIndexFieldName} to change the index
+        ///  field name for certain dimensions. 
+        /// </summary>
+        public FastTaxonomyFacetCounts(string indexFieldName, TaxonomyReader taxoReader, FacetsConfig config, FacetsCollector fc)
+            : base(indexFieldName, taxoReader, config)
+        {
+            Count(fc.GetMatchingDocs);
+        }
+
+        private void Count(IList<FacetsCollector.MatchingDocs> matchingDocs)
+        {
+            foreach (FacetsCollector.MatchingDocs hits in matchingDocs)
+            {
+                BinaryDocValues dv = hits.context.AtomicReader.GetBinaryDocValues(IndexFieldName);
+                if (dv == null) // this reader does not have DocValues for the requested category list
+                {
+                    continue;
+                }
+
+                DocIdSetIterator docs = hits.bits.GetIterator();
+
+                int doc;
+                BytesRef bytesRef = new BytesRef();
+                while ((doc = docs.NextDoc()) != DocIdSetIterator.NO_MORE_DOCS)
+                {
+                    dv.Get(doc,bytesRef);
+                    sbyte[] bytes = bytesRef.Bytes;
+                    int end = bytesRef.Offset + bytesRef.Length;
+                    int ord = 0;
+                    int offset = bytesRef.Offset;
+                    int prev = 0;
+                    while (offset < end)
+                    {
+                        sbyte b = bytes[offset++];
+                        if (b >= 0)
+                        {
+                            prev = ord = ((ord << 7) | b) + prev;
+                            ++values[ord];
+                            ord = 0;
+                        }
+                        else
+                        {
+                            ord = (ord << 7) | (b & 0x7F);
+                        }
+                    }
+                }
+            }
+
+            Rollup();
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/FloatAssociationFacetField.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/FloatAssociationFacetField.cs b/Lucene.Net.Facet/Taxonomy/FloatAssociationFacetField.cs
new file mode 100644
index 0000000..cc90e61
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/FloatAssociationFacetField.cs
@@ -0,0 +1,65 @@
+using Lucene.Net.Support;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+	/*
+	 * 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 Document = Lucene.Net.Documents.Document;
+	using BytesRef = Lucene.Net.Util.BytesRef;
+
+	/// <summary>
+	/// Add an instance of this to your <seealso cref="Document"/> to add
+	///  a facet label associated with a float.  Use {@link
+	///  TaxonomyFacetSumFloatAssociations} to aggregate float values
+	///  per facet label at search time.
+	/// 
+	///  @lucene.experimental 
+	/// </summary>
+	public class FloatAssociationFacetField : AssociationFacetField
+	{
+
+	  /// <summary>
+	  /// Creates this from {@code dim} and {@code path} and a
+	  ///  float association 
+	  /// </summary>
+	  public FloatAssociationFacetField(float assoc, string dim, params string[] path) : base(floatToBytesRef(assoc), dim, path)
+	  {
+	  }
+
+	  /// <summary>
+	  /// Encodes a {@code float} as a 4-byte <seealso cref="BytesRef"/>. </summary>
+	  public static BytesRef floatToBytesRef(float v)
+	  {
+		return IntAssociationFacetField.intToBytesRef(Number.FloatToIntBits(v));
+	  }
+
+	  /// <summary>
+	  /// Decodes a previously encoded {@code float}. </summary>
+	  public static float bytesRefToFloat(BytesRef b)
+	  {
+		return Number.IntBitsToFloat(IntAssociationFacetField.bytesRefToInt(b));
+	  }
+
+	  public override string ToString()
+	  {
+		return "FloatAssociationFacetField(dim=" + dim + " path=" + Arrays.ToString(path) + " value=" + bytesRefToFloat(assoc) + ")";
+	  }
+	}
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/FloatTaxonomyFacets.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/FloatTaxonomyFacets.cs b/Lucene.Net.Facet/Taxonomy/FloatTaxonomyFacets.cs
new file mode 100644
index 0000000..9dbe71d
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/FloatTaxonomyFacets.cs
@@ -0,0 +1,183 @@
+using System;
+using System.Diagnostics;
+using System.Collections.Generic;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * 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 DimConfig = Lucene.Net.Facet.FacetsConfig.DimConfig;
+
+    /// <summary>
+    /// Base class for all taxonomy-based facets that aggregate
+    ///  to a per-ords float[]. 
+    /// </summary>
+    public abstract class FloatTaxonomyFacets : TaxonomyFacets
+    {
+
+        /// <summary>
+        /// Per-ordinal value. </summary>
+        protected readonly float[] values;
+
+        /// <summary>
+        /// Sole constructor. </summary>
+        protected internal FloatTaxonomyFacets(string indexFieldName, TaxonomyReader taxoReader, FacetsConfig config)
+            : base(indexFieldName, taxoReader, config)
+        {
+            values = new float[taxoReader.Size];
+        }
+
+        /// <summary>
+        /// Rolls up any single-valued hierarchical dimensions. </summary>
+        protected virtual void Rollup()
+        {
+            // Rollup any necessary dims:
+            foreach (KeyValuePair<string, FacetsConfig.DimConfig> ent in Config.DimConfigs)
+            {
+                string dim = ent.Key;
+                FacetsConfig.DimConfig ft = ent.Value;
+                if (ft.hierarchical && ft.multiValued == false)
+                {
+                    int dimRootOrd = TaxoReader.GetOrdinal(new FacetLabel(dim));
+                    Debug.Assert(dimRootOrd > 0);
+                    values[dimRootOrd] += Rollup(Children[dimRootOrd]);
+                }
+            }
+        }
+
+        private float Rollup(int ord)
+        {
+            float sum = 0;
+            while (ord != TaxonomyReader.INVALID_ORDINAL)
+            {
+                float childValue = values[ord] + Rollup(Children[ord]);
+                values[ord] = childValue;
+                sum += childValue;
+                ord = Siblings[ord];
+            }
+            return sum;
+        }
+
+        public override float GetSpecificValue(string dim, params string[] path)
+        {
+            FacetsConfig.DimConfig dimConfig = VerifyDim(dim);
+            if (path.Length == 0)
+            {
+                if (dimConfig.hierarchical && dimConfig.multiValued == false)
+                {
+                    // ok: rolled up at search time
+                }
+                else if (dimConfig.requireDimCount && dimConfig.multiValued)
+                {
+                    // ok: we indexed all ords at index time
+                }
+                else
+                {
+                    throw new System.ArgumentException("cannot return dimension-level value alone; use getTopChildren instead");
+                }
+            }
+            int ord = TaxoReader.GetOrdinal(new FacetLabel(dim, path));
+            if (ord < 0)
+            {
+                return -1;
+            }
+            return values[ord];
+        }
+
+        public override FacetResult GetTopChildren(int topN, string dim, params string[] path)
+        {
+            if (topN <= 0)
+            {
+                throw new System.ArgumentException("topN must be > 0 (got: " + topN + ")");
+            }
+            FacetsConfig.DimConfig dimConfig = VerifyDim(dim);
+            FacetLabel cp = new FacetLabel(dim, path);
+            int dimOrd = TaxoReader.GetOrdinal(cp);
+            if (dimOrd == -1)
+            {
+                return null;
+            }
+
+            TopOrdAndFloatQueue q = new TopOrdAndFloatQueue(Math.Min(TaxoReader.Size, topN));
+            float bottomValue = 0;
+
+            int ord = Children[dimOrd];
+            float sumValues = 0;
+            int childCount = 0;
+
+            TopOrdAndFloatQueue.OrdAndValue reuse = null;
+            while (ord != TaxonomyReader.INVALID_ORDINAL)
+            {
+                if (values[ord] > 0)
+                {
+                    sumValues += values[ord];
+                    childCount++;
+                    if (values[ord] > bottomValue)
+                    {
+                        if (reuse == null)
+                        {
+                            reuse = new TopOrdAndFloatQueue.OrdAndValue();
+                        }
+                        reuse.ord = ord;
+                        reuse.value = values[ord];
+                        reuse = q.InsertWithOverflow(reuse);
+                        if (q.Size() == topN)
+                        {
+                            bottomValue = q.Top().value;
+                        }
+                    }
+                }
+
+                ord = Siblings[ord];
+            }
+
+            if (sumValues == 0)
+            {
+                return null;
+            }
+
+            if (dimConfig.multiValued)
+            {
+                if (dimConfig.requireDimCount)
+                {
+                    sumValues = values[dimOrd];
+                }
+                else
+                {
+                    // Our sum'd count is not correct, in general:
+                    sumValues = -1;
+                }
+            }
+            else
+            {
+                // Our sum'd dim count is accurate, so we keep it
+            }
+
+            LabelAndValue[] labelValues = new LabelAndValue[q.Size()];
+            for (int i = labelValues.Length - 1; i >= 0; i--)
+            {
+                TopOrdAndFloatQueue.OrdAndValue ordAndValue = q.Pop();
+                FacetLabel child = TaxoReader.GetPath(ordAndValue.ord);
+                labelValues[i] = new LabelAndValue(child.components[cp.length], ordAndValue.value);
+            }
+
+            return new FacetResult(dim, path, sumValues, labelValues, childCount);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/IntAssociationFacetField.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/IntAssociationFacetField.cs b/Lucene.Net.Facet/Taxonomy/IntAssociationFacetField.cs
new file mode 100644
index 0000000..b3f1ddd
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/IntAssociationFacetField.cs
@@ -0,0 +1,74 @@
+using Lucene.Net.Support;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * 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 Document = Lucene.Net.Documents.Document;
+    using BytesRef = Lucene.Net.Util.BytesRef;
+
+    /// <summary>
+    /// Add an instance of this to your <seealso cref="Document"/> to add
+    ///  a facet label associated with an int.  Use {@link
+    ///  TaxonomyFacetSumIntAssociations} to aggregate int values
+    ///  per facet label at search time.
+    /// 
+    ///  @lucene.experimental 
+    /// </summary>
+    public class IntAssociationFacetField : AssociationFacetField
+    {
+
+        /// <summary>
+        /// Creates this from {@code dim} and {@code path} and an
+        ///  int association 
+        /// </summary>
+        public IntAssociationFacetField(int assoc, string dim, params string[] path)
+            : base(intToBytesRef(assoc), dim, path)
+        {
+        }
+
+        /// <summary>
+        /// Encodes an {@code int} as a 4-byte <seealso cref="BytesRef"/>,
+        ///  big-endian. 
+        /// </summary>
+        public static BytesRef intToBytesRef(int v)
+        {
+            sbyte[] bytes = new sbyte[4];
+            // big-endian:
+            bytes[0] = (sbyte)(v >> 24);
+            bytes[1] = (sbyte)(v >> 16);
+            bytes[2] = (sbyte)(v >> 8);
+            bytes[3] = (sbyte)v;
+            return new BytesRef(bytes);
+        }
+
+        /// <summary>
+        /// Decodes a previously encoded {@code int}. </summary>
+        public static int bytesRefToInt(BytesRef b)
+        {
+            return ((b.Bytes[b.Offset] & 0xFF) << 24) | ((b.Bytes[b.Offset + 1] & 0xFF) << 16) | ((b.Bytes[b.Offset + 2] & 0xFF) << 8) | (b.Bytes[b.Offset + 3] & 0xFF);
+        }
+
+        public override string ToString()
+        {
+            return "IntAssociationFacetField(dim=" + dim + " path=" + Arrays.ToString(path) + " value=" + bytesRefToInt(assoc) + ")";
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/IntTaxonomyFacets.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/IntTaxonomyFacets.cs b/Lucene.Net.Facet/Taxonomy/IntTaxonomyFacets.cs
new file mode 100644
index 0000000..ad40137
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/IntTaxonomyFacets.cs
@@ -0,0 +1,189 @@
+using System;
+using System.Collections.Generic;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * 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 DimConfig = Lucene.Net.Facet.FacetsConfig.DimConfig;
+
+    /// <summary>
+    /// Base class for all taxonomy-based facets that aggregate
+    ///  to a per-ords int[]. 
+    /// </summary>
+
+    public abstract class IntTaxonomyFacets : TaxonomyFacets
+    {
+
+        /// <summary>
+        /// Per-ordinal value. </summary>
+        protected internal readonly int[] values;
+
+        /// <summary>
+        /// Sole constructor. </summary>
+        protected internal IntTaxonomyFacets(string indexFieldName, TaxonomyReader taxoReader, FacetsConfig config)
+            : base(indexFieldName, taxoReader, config)
+        {
+            values = new int[taxoReader.Size];
+        }
+
+        /// <summary>
+        /// Rolls up any single-valued hierarchical dimensions. </summary>
+        protected virtual void Rollup()
+        {
+            // Rollup any necessary dims:
+            foreach (KeyValuePair<string, FacetsConfig.DimConfig> ent in Config.DimConfigs)
+            {
+                string dim = ent.Key;
+                FacetsConfig.DimConfig ft = ent.Value;
+                if (ft.hierarchical && ft.multiValued == false)
+                {
+                    int dimRootOrd = TaxoReader.GetOrdinal(new FacetLabel(dim));
+                    // It can be -1 if this field was declared in the
+                    // config but never indexed:
+                    if (dimRootOrd > 0)
+                    {
+                        values[dimRootOrd] += Rollup(Children[dimRootOrd]);
+                    }
+                }
+            }
+        }
+
+        private int Rollup(int ord)
+        {
+            int sum = 0;
+            while (ord != TaxonomyReader.INVALID_ORDINAL)
+            {
+                int childValue = values[ord] + Rollup(Children[ord]);
+                values[ord] = childValue;
+                sum += childValue;
+                ord = Siblings[ord];
+            }
+            return sum;
+        }
+
+        public override float GetSpecificValue(string dim, params string[] path)
+        {
+            var dimConfig = VerifyDim(dim);
+            if (path.Length == 0)
+            {
+                if (dimConfig.hierarchical && dimConfig.multiValued == false)
+                {
+                    // ok: rolled up at search time
+                }
+                else if (dimConfig.requireDimCount && dimConfig.multiValued)
+                {
+                    // ok: we indexed all ords at index time
+                }
+                else
+                {
+                    throw new System.ArgumentException("cannot return dimension-level value alone; use getTopChildren instead");
+                }
+            }
+            int ord = TaxoReader.GetOrdinal(new FacetLabel(dim, path));
+            if (ord < 0)
+            {
+                return -1;
+            }
+            return values[ord];
+        }
+
+        public override FacetResult GetTopChildren(int topN, string dim, params string[] path)
+        {
+            if (topN <= 0)
+            {
+                throw new System.ArgumentException("topN must be > 0 (got: " + topN + ")");
+            }
+            var dimConfig = VerifyDim(dim);
+            FacetLabel cp = new FacetLabel(dim, path);
+            int dimOrd = TaxoReader.GetOrdinal(cp);
+            if (dimOrd == -1)
+            {
+                return null;
+            }
+
+            TopOrdAndIntQueue q = new TopOrdAndIntQueue(Math.Min(TaxoReader.Size, topN));
+
+            int bottomValue = 0;
+
+            int ord = Children[dimOrd];
+            int totValue = 0;
+            int childCount = 0;
+
+            TopOrdAndIntQueue.OrdAndValue reuse = null;
+            while (ord != TaxonomyReader.INVALID_ORDINAL)
+            {
+                if (values[ord] > 0)
+                {
+                    totValue += values[ord];
+                    childCount++;
+                    if (values[ord] > bottomValue)
+                    {
+                        if (reuse == null)
+                        {
+                            reuse = new TopOrdAndIntQueue.OrdAndValue();
+                        }
+                        reuse.ord = ord;
+                        reuse.value = values[ord];
+                        reuse = q.InsertWithOverflow(reuse);
+                        if (q.Size() == topN)
+                        {
+                            bottomValue = q.Top().value;
+                        }
+                    }
+                }
+
+                ord = Siblings[ord];
+            }
+
+            if (totValue == 0)
+            {
+                return null;
+            }
+
+            if (dimConfig.multiValued)
+            {
+                if (dimConfig.requireDimCount)
+                {
+                    totValue = values[dimOrd];
+                }
+                else
+                {
+                    // Our sum'd value is not correct, in general:
+                    totValue = -1;
+                }
+            }
+            else
+            {
+                // Our sum'd dim value is accurate, so we keep it
+            }
+
+            LabelAndValue[] labelValues = new LabelAndValue[q.Size()];
+            for (int i = labelValues.Length - 1; i >= 0; i--)
+            {
+                TopOrdAndIntQueue.OrdAndValue ordAndValue = q.Pop();
+                FacetLabel child = TaxoReader.GetPath(ordAndValue.ord);
+                labelValues[i] = new LabelAndValue(child.components[cp.length], ordAndValue.value);
+            }
+
+            return new FacetResult(dim, path, totValue, labelValues, childCount);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/LRUHashMap.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/LRUHashMap.cs b/Lucene.Net.Facet/Taxonomy/LRUHashMap.cs
new file mode 100644
index 0000000..d442992
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/LRUHashMap.cs
@@ -0,0 +1,154 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using Lucene.Net.Support;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * 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.
+     */
+
+
+    /// <summary>
+    /// LRUHashMap is an extension of Java's HashMap, which has a bounded size();
+    /// When it reaches that size, each time a new element is added, the least
+    /// recently used (LRU) entry is removed.
+    /// <para>
+    /// Java makes it very easy to implement LRUHashMap - all its functionality is
+    /// already available from <seealso cref="java.util.LinkedHashMap"/>, and we just need to
+    /// configure that properly.
+    /// </para>
+    /// <para>
+    /// Note that like HashMap, LRUHashMap is unsynchronized, and the user MUST
+    /// synchronize the access to it if used from several threads. Moreover, while
+    /// with HashMap this is only a concern if one of the threads is modifies the
+    /// map, with LURHashMap every read is a modification (because the LRU order
+    /// needs to be remembered) so proper synchronization is always necessary.
+    /// </para>
+    /// <para>
+    /// With the usual synchronization mechanisms available to the user, this
+    /// unfortunately means that LRUHashMap will probably perform sub-optimally under
+    /// heavy contention: while one thread uses the hash table (reads or writes), any
+    /// other thread will be blocked from using it - or even just starting to use it
+    /// (e.g., calculating the hash function). A more efficient approach would be not
+    /// to use LinkedHashMap at all, but rather to use a non-locking (as much as
+    /// possible) thread-safe solution, something along the lines of
+    /// java.util.concurrent.ConcurrentHashMap (though that particular class does not
+    /// support the additional LRU semantics, which will need to be added separately
+    /// using a concurrent linked list or additional storage of timestamps (in an
+    /// array or inside the entry objects), or whatever).
+    /// 
+    /// @lucene.experimental
+    /// </para>
+    /// </summary>
+    public class LRUHashMap<TV, TU> where TU : class //this is implementation of LRU Cache
+    {
+
+        public int MaxSize { get; set; }
+        private int CleanSize;
+        private TimeSpan MaxDuration;
+
+
+        private readonly ConcurrentDictionary<TV, CacheDataObject<TU>> _cache = new ConcurrentDictionary<TV, CacheDataObject<TU>>();
+
+        public LRUHashMap(int maxSize = 50000, int cleanPercentage = 30, TimeSpan maxDuration = default(TimeSpan))
+        {
+            MaxSize = maxSize;
+            CleanSize = (int)Math.Max(MaxSize * (1.0 * cleanPercentage / 100), 1);
+            if (maxDuration == default(TimeSpan))
+            {
+                MaxDuration = TimeSpan.FromDays(1);
+            }
+            else
+            {
+                MaxDuration = maxDuration;
+            }
+        }
+
+        
+        public bool Put(TV cacheKey, TU value)
+        {
+            return AddToCache(cacheKey, value);
+        }
+
+        public bool AddToCache(TV cacheKey, TU value)
+        {
+            var cachedResult = new CacheDataObject<TU>
+            {
+                Usage = 1, //value == null ? 1 : value.Usage + 1,
+                Value = value,
+                Timestamp = DateTime.UtcNow
+            };
+
+            _cache.AddOrUpdate(cacheKey, cachedResult, (_, __) => cachedResult);
+            if (_cache.Count > MaxSize)
+            {
+                foreach (var source in _cache
+                    .OrderByDescending(x => x.Value.Usage)
+                    .ThenBy(x => x.Value.Timestamp)
+                    .Skip(MaxSize - CleanSize))
+                {
+                    if (EqualityComparer<TV>.Default.Equals(source.Key, cacheKey))
+                        continue; // we don't want to remove the one we just added
+                    CacheDataObject<TU> ignored;
+                    _cache.TryRemove(source.Key, out ignored);
+                }
+            }
+            return true;
+        }
+
+        public TU Get(TV cacheKey, bool increment = false)
+        {
+            CacheDataObject<TU> value;
+            if (_cache.TryGetValue(cacheKey, out value) && (DateTime.UtcNow - value.Timestamp) <= MaxDuration)
+            {
+                if (increment)
+                {
+                    Interlocked.Increment(ref value.Usage);
+                }
+                return value.Value;
+            }
+            return null;
+        }
+
+        public bool IsExistInCache(TV cacheKey)
+        {
+            return (_cache.ContainsKey(cacheKey));
+        }
+
+        public int Size()
+        {
+            return _cache.Count;
+        }
+
+        #region Nested type: CacheDataObject
+
+        private class CacheDataObject<T> where T : class
+        {
+            public DateTime Timestamp;
+            public int Usage;
+            public T Value;
+        }
+
+        #endregion
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/OrdinalsReader.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/OrdinalsReader.cs b/Lucene.Net.Facet/Taxonomy/OrdinalsReader.cs
new file mode 100644
index 0000000..544a1ef
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/OrdinalsReader.cs
@@ -0,0 +1,68 @@
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements.  See the NOTICE file distributed with
+     * this work for additional information regarding copyright ownership.
+     * The ASF licenses this file to You under the Apache License, Version 2.0
+     * (the "License"); you may not use this file except in compliance with
+     * the License.  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     */
+
+    using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext;
+    using IntsRef = Lucene.Net.Util.IntsRef;
+
+    /// <summary>
+    /// Provides per-document ordinals. 
+    /// </summary>
+
+    public abstract class OrdinalsReader
+    {
+
+        /// <summary>
+        /// Returns ordinals for documents in one segment. </summary>
+        public abstract class OrdinalsSegmentReader
+        {
+            /// <summary>
+            /// Get the ordinals for this document.  ordinals.offset
+            ///  must always be 0! 
+            /// </summary>
+            public abstract void Get(int doc, IntsRef ordinals);
+
+            /// <summary>
+            /// Default constructor. 
+            /// </summary>
+            public OrdinalsSegmentReader()
+            {
+            }
+        }
+
+        /// <summary>
+        /// Default constructor. 
+        /// </summary>
+        public OrdinalsReader()
+        {
+        }
+
+        /// <summary>
+        /// Set current atomic reader. 
+        /// </summary>
+        public abstract OrdinalsSegmentReader GetReader(AtomicReaderContext context);
+
+        /// <summary>
+        /// Returns the indexed field name this {@code
+        ///  OrdinalsReader} is reading from. 
+        /// </summary>
+        public abstract string IndexFieldName { get; }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/ParallelTaxonomyArrays.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/ParallelTaxonomyArrays.cs b/Lucene.Net.Facet/Taxonomy/ParallelTaxonomyArrays.cs
new file mode 100644
index 0000000..26f1d8a
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/ParallelTaxonomyArrays.cs
@@ -0,0 +1,74 @@
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+
+	/*
+	 * 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.
+	 */
+
+	/// <summary>
+	/// Returns 3 arrays for traversing the taxonomy:
+	/// <ul>
+	/// <li>{@code parents}: {@code parents[i]} denotes the parent of category
+	/// ordinal {@code i}.</li>
+	/// <li>{@code children}: {@code children[i]} denotes a child of category ordinal
+	/// {@code i}.</li>
+	/// <li>{@code siblings}: {@code siblings[i]} denotes the sibling of category
+	/// ordinal {@code i}.</li>
+	/// </ul>
+	/// 
+	/// To traverse the taxonomy tree, you typically start with {@code children[0]}
+	/// (ordinal 0 is reserved for ROOT), and then depends if you want to do DFS or
+	/// BFS, you call {@code children[children[0]]} or {@code siblings[children[0]]}
+	/// and so forth, respectively.
+	/// 
+	/// <para>
+	/// <b>NOTE:</b> you are not expected to modify the values of the arrays, since
+	/// the arrays are shared with other threads.
+	/// 
+	/// @lucene.experimental
+	/// </para>
+	/// </summary>
+	public abstract class ParallelTaxonomyArrays
+	{
+
+	  /// <summary>
+	  /// Sole constructor. </summary>
+	  public ParallelTaxonomyArrays()
+	  {
+	  }
+
+	  /// <summary>
+	  /// Returns the parents array, where {@code parents[i]} denotes the parent of
+	  /// category ordinal {@code i}.
+	  /// </summary>
+	  public abstract int[] Parents();
+
+	  /// <summary>
+	  /// Returns the children array, where {@code children[i]} denotes a child of
+	  /// category ordinal {@code i}.
+	  /// </summary>
+	  public abstract int[] Children();
+
+	  /// <summary>
+	  /// Returns the siblings array, where {@code siblings[i]} denotes the sibling
+	  /// of category ordinal {@code i}.
+	  /// </summary>
+	  public abstract int[] Siblings();
+
+	}
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/PrintTaxonomyStats.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/PrintTaxonomyStats.cs b/Lucene.Net.Facet/Taxonomy/PrintTaxonomyStats.cs
new file mode 100644
index 0000000..bfcbda7
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/PrintTaxonomyStats.cs
@@ -0,0 +1,121 @@
+using System;
+using System.IO;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * 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 ChildrenIterator = Lucene.Net.Facet.Taxonomy.TaxonomyReader.ChildrenIterator;
+    using DirectoryTaxonomyReader = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyReader;
+    using Directory = Lucene.Net.Store.Directory;
+    using FSDirectory = Lucene.Net.Store.FSDirectory;
+
+    /// <summary>
+    /// Prints how many ords are under each dimension. </summary>
+
+    // java -cp ../build/core/classes/java:../build/facet/classes/java org.apache.lucene.facet.util.PrintTaxonomyStats -printTree /s2/scratch/indices/wikibig.trunk.noparents.facets.Lucene41.nd1M/facets
+    public class PrintTaxonomyStats
+    {
+
+        /// <summary>
+        /// Sole constructor. </summary>
+        public PrintTaxonomyStats()
+        {
+        }
+
+        /// <summary>
+        /// Command-line tool. </summary>
+        public static void Main(string[] args)
+        {
+            bool printTree = false;
+            string path = null;
+            for (int i = 0; i < args.Length; i++)
+            {
+                if (args[i].Equals("-printTree"))
+                {
+                    printTree = true;
+                }
+                else
+                {
+                    path = args[i];
+                }
+            }
+            if (args.Length != (printTree ? 2 : 1))
+            {
+                Console.WriteLine("\nUsage: java -classpath ... org.apache.lucene.facet.util.PrintTaxonomyStats [-printTree] /path/to/taxononmy/index\n");
+                Environment.Exit(1);
+            }
+            Store.Directory dir = FSDirectory.Open(new DirectoryInfo(path));
+            var r = new DirectoryTaxonomyReader(dir);
+            PrintStats(r, System.Console.Out, printTree);
+            r.Dispose();
+            //dir.close();
+        }
+
+        /// <summary>
+        /// Recursively prints stats for all ordinals. </summary>
+        public static void PrintStats(TaxonomyReader r, TextWriter @out, bool printTree)
+        {
+            @out.WriteLine(r.Size + " total categories.");
+
+            ChildrenIterator it = r.GetChildren(TaxonomyReader.ROOT_ORDINAL);
+            int child;
+            while ((child = it.Next()) != TaxonomyReader.INVALID_ORDINAL)
+            {
+                ChildrenIterator chilrenIt = r.GetChildren(child);
+                int numImmediateChildren = 0;
+                while (chilrenIt.Next() != TaxonomyReader.INVALID_ORDINAL)
+                {
+                    numImmediateChildren++;
+                }
+                FacetLabel cp = r.GetPath(child);
+                @out.WriteLine("/" + cp.components[0] + ": " + numImmediateChildren + " immediate children; " + (1 + CountAllChildren(r, child)) + " total categories");
+                if (printTree)
+                {
+                    PrintAllChildren(@out, r, child, "  ", 1);
+                }
+            }
+        }
+
+        private static int CountAllChildren(TaxonomyReader r, int ord)
+        {
+            int count = 0;
+            ChildrenIterator it = r.GetChildren(ord);
+            int child;
+            while ((child = it.Next()) != TaxonomyReader.INVALID_ORDINAL)
+            {
+                count += 1 + CountAllChildren(r, child);
+            }
+            return count;
+        }
+
+        private static void PrintAllChildren(TextWriter @out, TaxonomyReader r, int ord, string indent, int depth)
+        {
+            ChildrenIterator it = r.GetChildren(ord);
+            int child;
+            while ((child = it.Next()) != TaxonomyReader.INVALID_ORDINAL)
+            {
+                @out.WriteLine(indent + "/" + r.GetPath(child).components[depth]);
+                PrintAllChildren(@out, r, child, indent + "  ", depth + 1);
+            }
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/SearcherTaxonomyManager.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/SearcherTaxonomyManager.cs b/Lucene.Net.Facet/Taxonomy/SearcherTaxonomyManager.cs
new file mode 100644
index 0000000..d543aad
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/SearcherTaxonomyManager.cs
@@ -0,0 +1,179 @@
+using System.Threading;
+using Lucene.Net.Search;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * 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 DirectoryTaxonomyReader = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyReader;
+    using DirectoryTaxonomyWriter = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter;
+    using DirectoryReader = Lucene.Net.Index.DirectoryReader;
+    using IndexReader = Lucene.Net.Index.IndexReader;
+    using IndexWriter = Lucene.Net.Index.IndexWriter;
+    using IndexSearcher = Lucene.Net.Search.IndexSearcher;
+    using Lucene.Net.Search;
+    using SearcherFactory = Lucene.Net.Search.SearcherFactory;
+    using SearcherManager = Lucene.Net.Search.SearcherManager;
+    using Directory = Lucene.Net.Store.Directory;
+    using IOUtils = Lucene.Net.Util.IOUtils;
+
+    /// <summary>
+    /// Manages near-real-time reopen of both an IndexSearcher
+    /// and a TaxonomyReader.
+    /// 
+    /// <para><b>NOTE</b>: If you call {@link
+    /// DirectoryTaxonomyWriter#replaceTaxonomy} then you must
+    /// open a new {@code SearcherTaxonomyManager} afterwards.
+    /// </para>
+    /// </summary>
+    public class SearcherTaxonomyManager : ReferenceManager<SearcherTaxonomyManager.SearcherAndTaxonomy>
+    {
+
+        /// <summary>
+        /// Holds a matched pair of <seealso cref="IndexSearcher"/> and
+        ///  <seealso cref="TaxonomyReader"/> 
+        /// </summary>
+        public class SearcherAndTaxonomy
+        {
+            /// <summary>
+            /// Point-in-time <seealso cref="IndexSearcher"/>. </summary>
+            public readonly IndexSearcher searcher;
+
+            /// <summary>
+            /// Matching point-in-time <seealso cref="DirectoryTaxonomyReader"/>. </summary>
+            public readonly DirectoryTaxonomyReader taxonomyReader;
+
+            /// <summary>
+            /// Create a SearcherAndTaxonomy </summary>
+            public SearcherAndTaxonomy(IndexSearcher searcher, DirectoryTaxonomyReader taxonomyReader)
+            {
+                this.searcher = searcher;
+                this.taxonomyReader = taxonomyReader;
+            }
+        }
+
+        private readonly SearcherFactory searcherFactory;
+        private readonly long taxoEpoch;
+        private readonly DirectoryTaxonomyWriter taxoWriter;
+
+        /// <summary>
+        /// Creates near-real-time searcher and taxonomy reader
+        ///  from the corresponding writers. 
+        /// </summary>
+        public SearcherTaxonomyManager(IndexWriter writer, bool applyAllDeletes, SearcherFactory searcherFactory, DirectoryTaxonomyWriter taxoWriter)
+        {
+            if (searcherFactory == null)
+            {
+                searcherFactory = new SearcherFactory();
+            }
+            this.searcherFactory = searcherFactory;
+            this.taxoWriter = taxoWriter;
+            var taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+            Current = new SearcherAndTaxonomy(SearcherManager.GetSearcher(searcherFactory, DirectoryReader.Open(writer, applyAllDeletes)), taxoReader);
+            this.taxoEpoch = taxoWriter.TaxonomyEpoch;
+        }
+
+        /// <summary>
+        /// Creates search and taxonomy readers over the corresponding directories.
+        /// 
+        /// <para>
+        /// <b>NOTE:</b> you should only use this constructor if you commit and call
+        /// <seealso cref="#maybeRefresh()"/> in the same thread. Otherwise it could lead to an
+        /// unsync'd <seealso cref="IndexSearcher"/> and <seealso cref="TaxonomyReader"/> pair.
+        /// </para>
+        /// </summary>
+        public SearcherTaxonomyManager(Store.Directory indexDir, Store.Directory taxoDir, SearcherFactory searcherFactory)
+        {
+            if (searcherFactory == null)
+            {
+                searcherFactory = new SearcherFactory();
+            }
+            this.searcherFactory = searcherFactory;
+            var taxoReader = new DirectoryTaxonomyReader(taxoDir);
+            Current = new SearcherAndTaxonomy(SearcherManager.GetSearcher(searcherFactory, DirectoryReader.Open(indexDir)), taxoReader);
+            this.taxoWriter = null;
+            taxoEpoch = -1;
+        }
+
+        protected override void DecRef(SearcherAndTaxonomy @ref)
+        {
+            @ref.searcher.IndexReader.DecRef();
+
+            // This decRef can fail, and then in theory we should
+            // tryIncRef the searcher to put back the ref count
+            // ... but 1) the below decRef should only fail because
+            // it decRef'd to 0 and closed and hit some IOException
+            // during close, in which case 2) very likely the
+            // searcher was also just closed by the above decRef and
+            // a tryIncRef would fail:
+            @ref.taxonomyReader.DecRef();
+        }
+
+        protected override bool TryIncRef(SearcherAndTaxonomy @ref)
+        {
+            if (@ref.searcher.IndexReader.TryIncRef())
+            {
+                if (@ref.taxonomyReader.TryIncRef())
+                {
+                    return true;
+                }
+                else
+                {
+                    @ref.searcher.IndexReader.DecRef();
+                }
+            }
+            return false;
+        }
+
+        protected override SearcherAndTaxonomy RefreshIfNeeded(SearcherAndTaxonomy @ref)
+        {
+            // Must re-open searcher first, otherwise we may get a
+            // new reader that references ords not yet known to the
+            // taxonomy reader:
+            IndexReader r = @ref.searcher.IndexReader;
+            IndexReader newReader = DirectoryReader.OpenIfChanged((DirectoryReader)r);
+            if (newReader == null)
+            {
+                return null;
+            }
+            else
+            {
+                var tr = TaxonomyReader.OpenIfChanged(@ref.taxonomyReader);
+                if (tr == null)
+                {
+                    @ref.taxonomyReader.IncRef();
+                    tr = @ref.taxonomyReader;
+                }
+                else if (taxoWriter != null && taxoWriter.TaxonomyEpoch != taxoEpoch)
+                {
+                    IOUtils.Close(newReader, tr);
+                    throw new ThreadStateException("DirectoryTaxonomyWriter.replaceTaxonomy was called, which is not allowed when using SearcherTaxonomyManager");
+                }
+
+                return new SearcherAndTaxonomy(SearcherManager.GetSearcher(searcherFactory, newReader), tr);
+            }
+        }
+
+        protected override int GetRefCount(SearcherAndTaxonomy reference)
+        {
+            return reference.searcher.IndexReader.RefCount;
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/TaxonomyFacetCounts.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/TaxonomyFacetCounts.cs b/Lucene.Net.Facet/Taxonomy/TaxonomyFacetCounts.cs
new file mode 100644
index 0000000..49be839
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/TaxonomyFacetCounts.cs
@@ -0,0 +1,77 @@
+using System.Collections.Generic;
+using Lucene.Net.Facet;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * 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 MatchingDocs = FacetsCollector.MatchingDocs;
+    using BinaryDocValues = Lucene.Net.Index.BinaryDocValues;
+    using DocIdSetIterator = Lucene.Net.Search.DocIdSetIterator;
+    using IntsRef = Lucene.Net.Util.IntsRef;
+
+    /// <summary>
+    /// Reads from any <seealso cref="OrdinalsReader"/>; use {@link
+    ///  FastTaxonomyFacetCounts} if you are using the
+    ///  default encoding from <seealso cref="BinaryDocValues"/>.
+    /// 
+    /// @lucene.experimental 
+    /// </summary>
+    public class TaxonomyFacetCounts : IntTaxonomyFacets
+    {
+        private readonly OrdinalsReader ordinalsReader;
+
+        /// <summary>
+        /// Create {@code TaxonomyFacetCounts}, which also
+        ///  counts all facet labels.  Use this for a non-default
+        ///  <seealso cref="OrdinalsReader"/>; otherwise use {@link
+        ///  FastTaxonomyFacetCounts}. 
+        /// </summary>
+        public TaxonomyFacetCounts(OrdinalsReader ordinalsReader, TaxonomyReader taxoReader, FacetsConfig config, FacetsCollector fc)
+            : base(ordinalsReader.IndexFieldName, taxoReader, config)
+        {
+            this.ordinalsReader = ordinalsReader;
+            Count(fc.GetMatchingDocs);
+        }
+
+        private void Count(IList<FacetsCollector.MatchingDocs> matchingDocs)
+        {
+            IntsRef scratch = new IntsRef();
+            foreach (FacetsCollector.MatchingDocs hits in matchingDocs)
+            {
+                OrdinalsReader.OrdinalsSegmentReader ords = ordinalsReader.GetReader(hits.context);
+                DocIdSetIterator docs = hits.bits.GetIterator();
+
+                int doc;
+                while ((doc = docs.NextDoc()) != DocIdSetIterator.NO_MORE_DOCS)
+                {
+                    ords.Get(doc, scratch);
+                    for (int i = 0; i < scratch.Length; i++)
+                    {
+                        values[scratch.Ints[scratch.Offset + i]]++;
+                    }
+                }
+            }
+
+            Rollup();
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/TaxonomyFacetSumFloatAssociations.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/TaxonomyFacetSumFloatAssociations.cs b/Lucene.Net.Facet/Taxonomy/TaxonomyFacetSumFloatAssociations.cs
new file mode 100644
index 0000000..bf0d417
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/TaxonomyFacetSumFloatAssociations.cs
@@ -0,0 +1,98 @@
+using System.Collections.Generic;
+using Lucene.Net.Facet;
+using Lucene.Net.Search;
+using Lucene.Net.Support;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * 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 MatchingDocs = FacetsCollector.MatchingDocs;
+    using BinaryDocValues = Lucene.Net.Index.BinaryDocValues;
+    using DocIdSetIterator = Lucene.Net.Search.DocIdSetIterator;
+    using BytesRef = Lucene.Net.Util.BytesRef;
+
+    /// <summary>
+    /// Aggregates sum of int values previously indexed with
+    ///  <seealso cref="FloatAssociationFacetField"/>, assuming the default
+    ///  encoding.
+    /// 
+    ///  @lucene.experimental 
+    /// </summary>
+    public class TaxonomyFacetSumFloatAssociations : FloatTaxonomyFacets
+    {
+
+        /// <summary>
+        /// Create {@code TaxonomyFacetSumFloatAssociations} against
+        ///  the default index field. 
+        /// </summary>
+        public TaxonomyFacetSumFloatAssociations(TaxonomyReader taxoReader, FacetsConfig config, FacetsCollector fc)
+            : this(FacetsConfig.DEFAULT_INDEX_FIELD_NAME, taxoReader, config, fc)
+        {
+        }
+
+        /// <summary>
+        /// Create {@code TaxonomyFacetSumFloatAssociations} against
+        ///  the specified index field. 
+        /// </summary>
+        public TaxonomyFacetSumFloatAssociations(string indexFieldName, TaxonomyReader taxoReader, FacetsConfig config, FacetsCollector fc)
+            : base(indexFieldName, taxoReader, config)
+        {
+            SumValues(fc.GetMatchingDocs);
+        }
+
+        private void SumValues(IList<FacetsCollector.MatchingDocs> matchingDocs)
+        {
+            //System.out.println("count matchingDocs=" + matchingDocs + " facetsField=" + facetsFieldName);
+            foreach (FacetsCollector.MatchingDocs hits in matchingDocs)
+            {
+                BinaryDocValues dv = hits.context.AtomicReader.GetBinaryDocValues(IndexFieldName);
+                if (dv == null) // this reader does not have DocValues for the requested category list
+                {
+                    continue;
+                }
+
+                DocIdSetIterator docs = hits.bits.GetIterator();
+
+                int doc;
+                while ((doc = docs.NextDoc()) != DocIdSetIterator.NO_MORE_DOCS)
+                {
+                    //System.out.println("  doc=" + doc);
+                    // TODO: use OrdinalsReader?  we'd need to add a
+                    // BytesRef getAssociation()?
+                    BytesRef bytesRef = new BytesRef();
+                    dv.Get(doc, bytesRef);
+                    sbyte[] bytes = bytesRef.Bytes;
+                    int end = bytesRef.Offset + bytesRef.Length;
+                    int offset = bytesRef.Offset;
+                    while (offset < end)
+                    {
+                        int ord = ((bytes[offset] & 0xFF) << 24) | ((bytes[offset + 1] & 0xFF) << 16) | ((bytes[offset + 2] & 0xFF) << 8) | (bytes[offset + 3] & 0xFF);
+                        offset += 4;
+                        int value = ((bytes[offset] & 0xFF) << 24) | ((bytes[offset + 1] & 0xFF) << 16) | ((bytes[offset + 2] & 0xFF) << 8) | (bytes[offset + 3] & 0xFF);
+                        offset += 4;
+                        values[ord] += Number.IntBitsToFloat(value);
+                    }
+                }
+            }
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/TaxonomyFacetSumIntAssociations.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/TaxonomyFacetSumIntAssociations.cs b/Lucene.Net.Facet/Taxonomy/TaxonomyFacetSumIntAssociations.cs
new file mode 100644
index 0000000..849cb30
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/TaxonomyFacetSumIntAssociations.cs
@@ -0,0 +1,96 @@
+using System.Collections.Generic;
+using Lucene.Net.Facet;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * 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 MatchingDocs = FacetsCollector.MatchingDocs;
+    using BinaryDocValues = Lucene.Net.Index.BinaryDocValues;
+    using DocIdSetIterator = Lucene.Net.Search.DocIdSetIterator;
+    using BytesRef = Lucene.Net.Util.BytesRef;
+
+    /// <summary>
+    /// Aggregates sum of int values previously indexed with
+    ///  <seealso cref="IntAssociationFacetField"/>, assuming the default
+    ///  encoding.
+    /// 
+    ///  @lucene.experimental 
+    /// </summary>
+    public class TaxonomyFacetSumIntAssociations : IntTaxonomyFacets
+    {
+
+        /// <summary>
+        /// Create {@code TaxonomyFacetSumIntAssociations} against
+        ///  the default index field. 
+        /// </summary>
+        public TaxonomyFacetSumIntAssociations(TaxonomyReader taxoReader, FacetsConfig config, FacetsCollector fc)
+            : this(FacetsConfig.DEFAULT_INDEX_FIELD_NAME, taxoReader, config, fc)
+        {
+        }
+
+        /// <summary>
+        /// Create {@code TaxonomyFacetSumIntAssociations} against
+        ///  the specified index field. 
+        /// </summary>
+        public TaxonomyFacetSumIntAssociations(string indexFieldName, TaxonomyReader taxoReader, FacetsConfig config, FacetsCollector fc)
+            : base(indexFieldName, taxoReader, config)
+        {
+            SumValues(fc.GetMatchingDocs);
+        }
+
+        private void SumValues(IList<FacetsCollector.MatchingDocs> matchingDocs)
+        {
+            //System.out.println("count matchingDocs=" + matchingDocs + " facetsField=" + facetsFieldName);
+            foreach (FacetsCollector.MatchingDocs hits in matchingDocs)
+            {
+                BinaryDocValues dv = hits.context.AtomicReader.GetBinaryDocValues(IndexFieldName);
+                if (dv == null) // this reader does not have DocValues for the requested category list
+                {
+                    continue;
+                }
+
+                DocIdSetIterator docs = hits.bits.GetIterator();
+
+                int doc;
+                while ((doc = docs.NextDoc()) != DocIdSetIterator.NO_MORE_DOCS)
+                {
+                    //System.out.println("  doc=" + doc);
+                    // TODO: use OrdinalsReader?  we'd need to add a
+                    // BytesRef getAssociation()?
+                    BytesRef bytesRef = new BytesRef();
+                    dv.Get(doc, bytesRef);
+                    sbyte[] bytes = bytesRef.Bytes;
+                    int end = bytesRef.Offset + bytesRef.Length;
+                    int offset = bytesRef.Offset;
+                    while (offset < end)
+                    {
+                        int ord = ((bytes[offset] & 0xFF) << 24) | ((bytes[offset + 1] & 0xFF) << 16) | ((bytes[offset + 2] & 0xFF) << 8) | (bytes[offset + 3] & 0xFF);
+                        offset += 4;
+                        int value = ((bytes[offset] & 0xFF) << 24) | ((bytes[offset + 1] & 0xFF) << 16) | ((bytes[offset + 2] & 0xFF) << 8) | (bytes[offset + 3] & 0xFF);
+                        offset += 4;
+                        values[ord] += value;
+                    }
+                }
+            }
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/TaxonomyFacetSumValueSource.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/TaxonomyFacetSumValueSource.cs b/Lucene.Net.Facet/Taxonomy/TaxonomyFacetSumValueSource.cs
new file mode 100644
index 0000000..4d4fc76
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/TaxonomyFacetSumValueSource.cs
@@ -0,0 +1,247 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using Lucene.Net.Facet;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * 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 MatchingDocs = FacetsCollector.MatchingDocs;
+    using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext;
+    using FunctionValues = Lucene.Net.Queries.Function.FunctionValues;
+    using ValueSource = Lucene.Net.Queries.Function.ValueSource;
+    using DoubleDocValues = Lucene.Net.Queries.Function.DocValues.DoubleDocValues;
+    using DocIdSetIterator = Lucene.Net.Search.DocIdSetIterator;
+    using Scorer = Lucene.Net.Search.Scorer;
+    using Weight = Lucene.Net.Search.Weight;
+    using IntsRef = Lucene.Net.Util.IntsRef;
+
+    /// <summary>
+    /// Aggregates sum of values from {@link
+    ///  FunctionValues#doubleVal}, for each facet label.
+    /// 
+    ///  @lucene.experimental 
+    /// </summary>
+    public class TaxonomyFacetSumValueSource : FloatTaxonomyFacets
+    {
+        private readonly OrdinalsReader ordinalsReader;
+
+        /// <summary>
+        /// Aggreggates float facet values from the provided
+        ///  <seealso cref="ValueSource"/>, pulling ordinals using {@link
+        ///  DocValuesOrdinalsReader} against the default indexed
+        ///  facet field {@link
+        ///  FacetsConfig#DEFAULT_INDEX_FIELD_NAME}. 
+        /// </summary>
+        public TaxonomyFacetSumValueSource(TaxonomyReader taxoReader, FacetsConfig config, FacetsCollector fc, ValueSource valueSource)
+            : this(new DocValuesOrdinalsReader(FacetsConfig.DEFAULT_INDEX_FIELD_NAME), taxoReader, config, fc, valueSource)
+        {
+        }
+
+        /// <summary>
+        /// Aggreggates float facet values from the provided
+        ///  <seealso cref="ValueSource"/>, and pulls ordinals from the
+        ///  provided <seealso cref="OrdinalsReader"/>. 
+        /// </summary>
+        public TaxonomyFacetSumValueSource(OrdinalsReader ordinalsReader, TaxonomyReader taxoReader, FacetsConfig config, FacetsCollector fc, ValueSource valueSource)
+            : base(ordinalsReader.IndexFieldName, taxoReader, config)
+        {
+            this.ordinalsReader = ordinalsReader;
+            SumValues(fc.GetMatchingDocs, fc.KeepScores, valueSource);
+        }
+
+        private sealed class FakeScorer : Scorer
+        {
+            internal float score_Renamed;
+            internal int docID_Renamed;
+            internal FakeScorer()
+                : base(null)
+            {
+            }
+            public override float Score()
+            {
+                return score_Renamed;
+            }
+            public override int Freq()
+            {
+                throw new System.NotSupportedException();
+            }
+            public override int DocID()
+            {
+                return docID_Renamed;
+            }
+            public override int NextDoc()
+            {
+                throw new System.NotSupportedException();
+            }
+            public override int Advance(int target)
+            {
+                throw new System.NotSupportedException();
+            }
+            public override long Cost()
+            {
+                return 0;
+            }
+            public override Weight Weight
+            {
+                get
+                {
+                    throw new System.NotSupportedException();
+                }
+            }
+
+            public override ICollection<ChildScorer> Children
+            {
+                get
+                {
+                    throw new System.NotSupportedException();
+                }
+            }
+        }
+
+        private void SumValues(IList<MatchingDocs> matchingDocs, bool keepScores, ValueSource valueSource)
+        {
+            FakeScorer scorer = new FakeScorer();
+            IDictionary context = new Dictionary<string, Scorer>();
+            if (keepScores)
+            {
+                context["scorer"] = scorer;
+            }
+            IntsRef scratch = new IntsRef();
+            foreach (MatchingDocs hits in matchingDocs)
+            {
+                OrdinalsReader.OrdinalsSegmentReader ords = ordinalsReader.GetReader(hits.context);
+
+                int scoresIdx = 0;
+                float[] scores = hits.scores;
+
+                FunctionValues functionValues = valueSource.GetValues(context, hits.context);
+                DocIdSetIterator docs = hits.bits.GetIterator();
+
+                int doc;
+                while ((doc = docs.NextDoc()) != DocIdSetIterator.NO_MORE_DOCS)
+                {
+                    ords.Get(doc, scratch);
+                    if (keepScores)
+                    {
+                        scorer.docID_Renamed = doc;
+                        scorer.score_Renamed = scores[scoresIdx++];
+                    }
+                    float value = (float)functionValues.DoubleVal(doc);
+                    for (int i = 0; i < scratch.Length; i++)
+                    {
+                        values[scratch.Ints[i]] += value;
+                    }
+                }
+            }
+
+            Rollup();
+        }
+
+        /// <summary>
+        /// <seealso cref="ValueSource"/> that returns the score for each
+        ///  hit; use this to aggregate the sum of all hit scores
+        ///  for each facet label.  
+        /// </summary>
+        public class ScoreValueSource : ValueSource
+        {
+
+            /// <summary>
+            /// Sole constructor. </summary>
+            public ScoreValueSource()
+            {
+            }
+
+            public override FunctionValues GetValues(IDictionary context, AtomicReaderContext readerContext)
+            {
+                Scorer scorer = (Scorer)context["scorer"];
+                if (scorer == null)
+                {
+                    throw new ThreadStateException("scores are missing; be sure to pass keepScores=true to FacetsCollector");
+                }
+                return new DoubleDocValuesAnonymousInnerClassHelper(this, scorer);
+            }
+
+            private class DoubleDocValuesAnonymousInnerClassHelper : DoubleDocValues
+            {
+                private readonly ScoreValueSource outerInstance;
+
+                private Scorer scorer;
+
+                public DoubleDocValuesAnonymousInnerClassHelper(ScoreValueSource outerInstance, Scorer scorer)
+                    : base(outerInstance)
+                {
+                    this.outerInstance = outerInstance;
+                    this.scorer = scorer;
+
+                }
+
+                public override double DoubleVal(int document)
+                {
+                    try
+                    {
+                        return scorer.Score();
+                    }
+                    catch (Exception exception)
+                    {
+                        throw;
+                    }
+                }
+            }
+
+            public override bool Equals(object o)
+            {
+                if (ReferenceEquals(null, o)) return false;
+                if (ReferenceEquals(this, o)) return true;
+                if (o.GetType() != this.GetType()) return false;
+                return Equals((ScoreValueSource) o);
+            }
+
+            protected bool Equals(ScoreValueSource other)
+            {
+                return Equals(this, other);
+            }
+
+            public override int GetHashCode()
+            {
+                return hcode;
+            }
+
+            private static readonly int hcode = typeof(DoubleDocValuesAnonymousInnerClassHelper).GetHashCode();
+
+            
+
+
+            public override string Description
+            {
+                get
+                {
+                    return "score()";
+                }
+
+            }
+        }
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/TaxonomyFacets.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/TaxonomyFacets.cs b/Lucene.Net.Facet/Taxonomy/TaxonomyFacets.cs
new file mode 100644
index 0000000..b23530d
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/TaxonomyFacets.cs
@@ -0,0 +1,137 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Lucene.Net.Facet;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * 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 DimConfig = Lucene.Net.Facet.FacetsConfig.DimConfig; // javadocs
+
+    /// <summary>
+    /// Base class for all taxonomy-based facets impls. </summary>
+    public abstract class TaxonomyFacets : Facets
+    {
+
+        private static readonly IComparer<FacetResult> BY_VALUE_THEN_DIM = new ComparatorAnonymousInnerClassHelper();
+
+        private class ComparatorAnonymousInnerClassHelper : IComparer<FacetResult>
+        {
+            public ComparatorAnonymousInnerClassHelper()
+            {
+            }
+
+            public virtual int Compare(FacetResult a, FacetResult b)
+            {
+                if ((double)a.value > (double)b.value)
+                {
+                    return -1;
+                }
+                else if ((double)b.value > (double)a.value)
+                {
+                    return 1;
+                }
+                else
+                {
+                    return a.dim.CompareTo(b.dim);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Index field name provided to the constructor. </summary>
+        protected internal readonly string IndexFieldName;
+
+        /// <summary>
+        /// {@code TaxonomyReader} provided to the constructor. </summary>
+        protected internal readonly TaxonomyReader TaxoReader;
+
+        /// <summary>
+        /// {@code FacetsConfig} provided to the constructor. </summary>
+        protected internal readonly FacetsConfig Config;
+
+        /// <summary>
+        /// Maps parent ordinal to its child, or -1 if the parent
+        ///  is childless. 
+        /// </summary>
+        protected internal readonly int[] Children;
+
+        /// <summary>
+        /// Maps an ordinal to its sibling, or -1 if there is no
+        ///  sibling. 
+        /// </summary>
+        protected internal readonly int[] Siblings;
+
+        /// <summary>
+        /// Sole constructor. 
+        /// </summary>
+        protected internal TaxonomyFacets(string indexFieldName, TaxonomyReader taxoReader, FacetsConfig config)
+        {
+            this.IndexFieldName = indexFieldName;
+            this.TaxoReader = taxoReader;
+            this.Config = config;
+            ParallelTaxonomyArrays pta = taxoReader.ParallelTaxonomyArrays;
+            Children = pta.Children();
+            Siblings = pta.Siblings();
+        }
+
+        /// <summary>
+        /// Throws {@code IllegalArgumentException} if the
+        ///  dimension is not recognized.  Otherwise, returns the
+        ///  <seealso cref="DimConfig"/> for this dimension. 
+        /// </summary>
+        protected internal virtual DimConfig VerifyDim(string dim)
+        {
+            DimConfig dimConfig = Config.GetDimConfig(dim);
+            if (!dimConfig.indexFieldName.Equals(IndexFieldName))
+            {
+                throw new System.ArgumentException("dimension \"" + dim + "\" was not indexed into field \"" + IndexFieldName);
+            }
+            return dimConfig;
+        }
+
+        public override IList<FacetResult> GetAllDims(int topN)
+        {
+            int ord = Children[TaxonomyReader.ROOT_ORDINAL];
+            IList<FacetResult> results = new List<FacetResult>();
+            while (ord != TaxonomyReader.INVALID_ORDINAL)
+            {
+                string dim = TaxoReader.GetPath(ord).components[0];
+                DimConfig dimConfig = Config.GetDimConfig(dim);
+                if (dimConfig.indexFieldName.Equals(IndexFieldName))
+                {
+                    FacetResult result = GetTopChildren(topN, dim);
+                    if (result != null)
+                    {
+                        results.Add(result);
+                    }
+                }
+                ord = Siblings[ord];
+            }
+
+            // Sort by highest value, tie break by dim:
+            var resultArray = results.ToArray();
+            Array.Sort(resultArray, BY_VALUE_THEN_DIM);
+            return resultArray;
+        }
+
+    }
+}
\ No newline at end of file


[10/26] lucenenet git commit: first commit of facet porting, failing tests will be fixed in next commits.

Posted by sy...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestConcurrentFacetedIndexing.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestConcurrentFacetedIndexing.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestConcurrentFacetedIndexing.cs
new file mode 100644
index 0000000..86dd3a9
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestConcurrentFacetedIndexing.cs
@@ -0,0 +1,228 @@
+using System;
+using System.Collections.Concurrent;
+using System.IO;
+using System.Threading;
+using Lucene.Net.Support;
+using NUnit.Framework;
+
+namespace Lucene.Net.Facet.Taxonomy.Directory
+{
+
+
+    using Document = Lucene.Net.Documents.Document;
+    using TaxonomyWriterCache = Lucene.Net.Facet.Taxonomy.WriterCache.TaxonomyWriterCache;
+    using Cl2oTaxonomyWriterCache = Lucene.Net.Facet.Taxonomy.WriterCache.Cl2oTaxonomyWriterCache;
+    using LruTaxonomyWriterCache = Lucene.Net.Facet.Taxonomy.WriterCache.LruTaxonomyWriterCache;
+    using IndexWriter = Lucene.Net.Index.IndexWriter;
+    using OpenMode = Lucene.Net.Index.IndexWriterConfig.OpenMode_e;
+    using Directory = Lucene.Net.Store.Directory;
+    using IOUtils = Lucene.Net.Util.IOUtils;
+
+    /*
+     * 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.
+     */
+    [TestFixture]
+    public class TestConcurrentFacetedIndexing : FacetTestCase
+    {
+
+        // A No-Op TaxonomyWriterCache which always discards all given categories, and
+        // always returns true in put(), to indicate some cache entries were cleared.
+        private static TaxonomyWriterCache NO_OP_CACHE = new TaxonomyWriterCacheAnonymousInnerClassHelper();
+
+        private class TaxonomyWriterCacheAnonymousInnerClassHelper : TaxonomyWriterCache
+        {
+            public TaxonomyWriterCacheAnonymousInnerClassHelper()
+            {
+            }
+
+
+            public virtual void Close()
+            {
+            }
+            public virtual int Get(FacetLabel categoryPath)
+            {
+                return -1;
+            }
+            public virtual bool Put(FacetLabel categoryPath, int ordinal)
+            {
+                return true;
+            }
+            public virtual bool Full
+            {
+                get
+                {
+                    return true;
+                }
+            }
+            public virtual void Clear()
+            {
+            }
+
+        }
+
+        internal static FacetField NewCategory()
+        {
+            Random r = Random();
+            string l1 = "l1." + r.Next(10); // l1.0-l1.9 (10 categories)
+            string l2 = "l2." + r.Next(30); // l2.0-l2.29 (30 categories)
+            string l3 = "l3." + r.Next(100); // l3.0-l3.99 (100 categories)
+            return new FacetField(l1, l2, l3);
+        }
+
+        internal static TaxonomyWriterCache NewTaxoWriterCache(int ndocs)
+        {
+            double d = Random().NextDouble();
+            if (d < 0.7)
+            {
+                // this is the fastest, yet most memory consuming
+                return new Cl2oTaxonomyWriterCache(1024, 0.15f, 3);
+            }
+            else if (TEST_NIGHTLY && d > 0.98)
+            {
+                // this is the slowest, but tests the writer concurrency when no caching is done.
+                // only pick it during NIGHTLY tests, and even then, with very low chances.
+                return NO_OP_CACHE;
+            }
+            else
+            {
+                // this is slower than CL2O, but less memory consuming, and exercises finding categories on disk too.
+                return new LruTaxonomyWriterCache(ndocs / 10);
+            }
+        }
+
+        [Test]
+        public virtual void TestConcurrency()
+        {
+            AtomicInteger numDocs = new AtomicInteger(AtLeast(10000));
+            Directory indexDir = NewDirectory();
+            Directory taxoDir = NewDirectory();
+            ConcurrentDictionary<string, string> values = new ConcurrentDictionary<string, string>();
+            IndexWriter iw = new IndexWriter(indexDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, null));
+            var tw = new DirectoryTaxonomyWriter(taxoDir, OpenMode.CREATE, NewTaxoWriterCache(numDocs.Get()));
+            ThreadClass[] indexThreads = new ThreadClass[AtLeast(4)];
+            FacetsConfig config = new FacetsConfig();
+            for (int i = 0; i < 10; i++)
+            {
+                config.SetHierarchical("l1." + i, true);
+                config.SetMultiValued("l1." + i, true);
+            }
+
+            for (int i = 0; i < indexThreads.Length; i++)
+            {
+                indexThreads[i] = new ThreadAnonymousInnerClassHelper(this, numDocs, values, iw, tw, config);
+            }
+
+            foreach (ThreadClass t in indexThreads)
+            {
+                t.Start();
+            }
+            foreach (ThreadClass t in indexThreads)
+            {
+                t.Join();
+            }
+
+            var tr = new DirectoryTaxonomyReader(tw);
+            // +1 for root category
+            if (values.Count + 1 != tr.Size)
+            {
+                foreach (string value in values.Keys)
+                {
+                    FacetLabel label = new FacetLabel(FacetsConfig.StringToPath(value));
+                    if (tr.GetOrdinal(label) == -1)
+                    {
+                        Console.WriteLine("FAIL: path=" + label + " not recognized");
+                    }
+                }
+                Fail("mismatch number of categories");
+            }
+            int[] parents = tr.ParallelTaxonomyArrays.Parents();
+            foreach (string cat in values.Keys)
+            {
+                FacetLabel cp = new FacetLabel(FacetsConfig.StringToPath(cat));
+                Assert.True(tr.GetOrdinal(cp) > 0, "category not found " + cp);
+                int level = cp.length;
+                int parentOrd = 0; // for root, parent is always virtual ROOT (ord=0)
+                FacetLabel path = null;
+                for (int i = 0; i < level; i++)
+                {
+                    path = cp.Subpath(i + 1);
+                    int ord = tr.GetOrdinal(path);
+                    Assert.AreEqual(parentOrd, parents[ord], "invalid parent for cp=" + path);
+                    parentOrd = ord; // next level should have this parent
+                }
+            }
+
+            IOUtils.Close(tw, iw, tr, taxoDir, indexDir);
+        }
+
+        private class ThreadAnonymousInnerClassHelper : ThreadClass
+        {
+            private readonly TestConcurrentFacetedIndexing outerInstance;
+
+            private AtomicInteger numDocs;
+            private ConcurrentDictionary<string, string> values;
+            private IndexWriter iw;
+            private Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter tw;
+            private FacetsConfig config;
+
+            public ThreadAnonymousInnerClassHelper(TestConcurrentFacetedIndexing outerInstance, AtomicInteger numDocs, ConcurrentDictionary<string, string> values, IndexWriter iw, Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter tw, FacetsConfig config)
+            {
+                this.outerInstance = outerInstance;
+                this.numDocs = numDocs;
+                this.values = values;
+                this.iw = iw;
+                this.tw = tw;
+                this.config = config;
+            }
+
+
+            public override void Run()
+            {
+                Random random = Random();
+                while (numDocs.DecrementAndGet() > 0)
+                {
+                    try
+                    {
+                        Document doc = new Document();
+                        int numCats = random.Next(3) + 1; // 1-3
+                        while (numCats-- > 0)
+                        {
+                            FacetField ff = NewCategory();
+                            doc.Add(ff);
+
+                            FacetLabel label = new FacetLabel(ff.dim, ff.path);
+                            // add all prefixes to values
+                            int level = label.length;
+                            while (level > 0)
+                            {
+                                string s = FacetsConfig.PathToString(label.components, level);
+                                values[s] = s;
+                                --level;
+                            }
+                        }
+                        iw.AddDocument(config.Build(tw, doc));
+                    }
+                    catch (IOException e)
+                    {
+                        throw new Exception(e.Message, e);
+                    }
+                }
+            }
+        }
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestDirectoryTaxonomyReader.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestDirectoryTaxonomyReader.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestDirectoryTaxonomyReader.cs
new file mode 100644
index 0000000..d933618
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestDirectoryTaxonomyReader.cs
@@ -0,0 +1,624 @@
+using System;
+using System.Collections.Generic;
+using Lucene.Net.Randomized.Generators;
+using Lucene.Net.Support;
+using NUnit.Framework;
+
+namespace Lucene.Net.Facet.Taxonomy.Directory
+{
+
+
+    using MockAnalyzer = Lucene.Net.Analysis.MockAnalyzer;
+    using ChildrenIterator = Lucene.Net.Facet.Taxonomy.TaxonomyReader.ChildrenIterator;
+    using IndexWriter = Lucene.Net.Index.IndexWriter;
+    using IndexWriterConfig = Lucene.Net.Index.IndexWriterConfig;
+    using OpenMode = Lucene.Net.Index.IndexWriterConfig.OpenMode_e;
+    using LogByteSizeMergePolicy = Lucene.Net.Index.LogByteSizeMergePolicy;
+    using LogMergePolicy = Lucene.Net.Index.LogMergePolicy;
+    using AlreadyClosedException = Lucene.Net.Store.AlreadyClosedException;
+    using Directory = Lucene.Net.Store.Directory;
+    using RAMDirectory = Lucene.Net.Store.RAMDirectory;
+    using IOUtils = Lucene.Net.Util.IOUtils;
+
+    /*
+     * 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.
+     */
+
+    public class TestDirectoryTaxonomyReader : FacetTestCase
+    {
+
+        [Test]
+        public virtual void TestCloseAfterIncRef()
+        {
+            Directory dir = NewDirectory();
+            var ltw = new DirectoryTaxonomyWriter(dir);
+            ltw.AddCategory(new FacetLabel("a"));
+            ltw.Dispose();
+
+            DirectoryTaxonomyReader ltr = new DirectoryTaxonomyReader(dir);
+            ltr.IncRef();
+            ltr.Dispose();
+
+            // should not fail as we IncRef() before close
+            var tmpSie = ltr.Size;
+            ltr.DecRef();
+
+            dir.Dispose();
+        }
+
+        [Test]
+        public virtual void TestCloseTwice()
+        {
+            Directory dir = NewDirectory();
+            var ltw = new DirectoryTaxonomyWriter(dir);
+            ltw.AddCategory(new FacetLabel("a"));
+            ltw.Dispose();
+
+            var ltr = new DirectoryTaxonomyReader(dir);
+            (ltr).Dispose();
+            (ltr).Dispose(); // no exception should be thrown
+
+            dir.Dispose();
+        }
+
+        [Test]
+        public virtual void TestOpenIfChangedResult()
+        {
+            Directory dir = null;
+            DirectoryTaxonomyWriter ltw = null;
+            DirectoryTaxonomyReader ltr = null;
+
+            try
+            {
+                dir = NewDirectory();
+                ltw = new DirectoryTaxonomyWriter(dir);
+
+                ltw.AddCategory(new FacetLabel("a"));
+                ltw.Commit();
+
+                ltr = new DirectoryTaxonomyReader(dir);
+                Assert.Null(TaxonomyReader.OpenIfChanged(ltr), "Nothing has changed");
+
+                ltw.AddCategory(new FacetLabel("b"));
+                ltw.Commit();
+
+                DirectoryTaxonomyReader newtr = TaxonomyReader.OpenIfChanged(ltr);
+                Assert.NotNull(newtr, "changes were committed");
+                Assert.Null(TaxonomyReader.OpenIfChanged(newtr), "Nothing has changed");
+                (newtr).Dispose();
+            }
+            finally
+            {
+                IOUtils.Close(ltw, ltr, dir);
+            }
+        }
+
+        [Test]
+        public virtual void TestAlreadyClosed()
+        {
+            Directory dir = NewDirectory();
+            var ltw = new DirectoryTaxonomyWriter(dir);
+            ltw.AddCategory(new FacetLabel("a"));
+            ltw.Dispose();
+
+            var ltr = new DirectoryTaxonomyReader(dir);
+            ltr.Dispose();
+            try
+            {
+                var tmpSize = ltr.Size;
+                Fail("An AlreadyClosedException should have been thrown here");
+            }
+            catch (AlreadyClosedException)
+            {
+                // good!
+            }
+            dir.Dispose();
+        }
+
+        /// <summary>
+        /// recreating a taxonomy should work well with a freshly opened taxonomy reader 
+        /// </summary>
+        [Test]
+        public virtual void TestFreshReadRecreatedTaxonomy()
+        {
+            doTestReadRecreatedTaxonomy(Random(), true);
+        }
+
+        [Test]
+        public virtual void TestOpenIfChangedReadRecreatedTaxonomy()
+        {
+            doTestReadRecreatedTaxonomy(Random(), false);
+        }
+        
+        private void doTestReadRecreatedTaxonomy(Random random, bool closeReader)
+        {
+            Directory dir = null;
+            TaxonomyWriter tw = null;
+            TaxonomyReader tr = null;
+
+            // prepare a few categories
+            int n = 10;
+            FacetLabel[] cp = new FacetLabel[n];
+            for (int i = 0; i < n; i++)
+            {
+                cp[i] = new FacetLabel("a", Convert.ToString(i));
+            }
+
+            try
+            {
+                dir = NewDirectory();
+
+                tw = new DirectoryTaxonomyWriter(dir);
+                tw.AddCategory(new FacetLabel("a"));
+                tw.Dispose();
+
+                tr = new DirectoryTaxonomyReader(dir);
+                int baseNumCategories = tr.Size;
+
+                for (int i = 0; i < n; i++)
+                {
+                    int k = random.Next(n);
+                    tw = new DirectoryTaxonomyWriter(dir, IndexWriterConfig.OpenMode_e.CREATE);
+                    for (int j = 0; j <= k; j++)
+                    {
+                        tw.AddCategory(cp[j]);
+                    }
+                    tw.Dispose();
+                    if (closeReader)
+                    {
+                        tr.Dispose(true);
+                        tr = new DirectoryTaxonomyReader(dir);
+                    }
+                    else
+                    {
+                        var newtr = TaxonomyReader.OpenIfChanged(tr);
+                        Assert.NotNull(newtr);
+                        tr.Dispose(true);
+                        tr = newtr;
+                    }
+                    Assert.AreEqual(baseNumCategories + 1 + k, tr.Size, "Wrong #categories in taxonomy (i=" + i + ", k=" + k + ")");
+                }
+            }
+            finally
+            {
+                IOUtils.Close(tr as DirectoryTaxonomyReader, tw, dir);
+            }
+        }
+
+        [Test]
+        public virtual void TestOpenIfChangedAndRefCount()
+        {
+            Directory dir = new RAMDirectory(); // no need for random directories here
+
+            var taxoWriter = new DirectoryTaxonomyWriter(dir);
+            taxoWriter.AddCategory(new FacetLabel("a"));
+            taxoWriter.Commit();
+
+            var taxoReader = new DirectoryTaxonomyReader(dir);
+            Assert.AreEqual(1, taxoReader.RefCount, "wrong refCount");
+
+            taxoReader.IncRef();
+            Assert.AreEqual(2, taxoReader.RefCount, "wrong refCount");
+
+            taxoWriter.AddCategory(new FacetLabel("a", "b"));
+            taxoWriter.Commit();
+            var newtr = TaxonomyReader.OpenIfChanged(taxoReader);
+            Assert.NotNull(newtr);
+            taxoReader.Dispose();
+            taxoReader = newtr;
+            Assert.AreEqual(1, taxoReader.RefCount, "wrong refCount");
+
+            taxoWriter.Dispose();
+            taxoReader.Dispose();
+            dir.Dispose();
+        }
+
+        [Test]
+        public virtual void TestOpenIfChangedManySegments()
+        {
+            // test openIfChanged() when the taxonomy contains many segments
+            Directory dir = NewDirectory();
+
+            DirectoryTaxonomyWriter writer = new DirectoryTaxonomyWriterAnonymousInnerClassHelper(this, dir);
+            var reader = new DirectoryTaxonomyReader(writer);
+
+            int numRounds = Random().Next(10) + 10;
+            int numCategories = 1; // one for root
+            for (int i = 0; i < numRounds; i++)
+            {
+                int numCats = Random().Next(4) + 1;
+                for (int j = 0; j < numCats; j++)
+                {
+                    writer.AddCategory(new FacetLabel(Convert.ToString(i), Convert.ToString(j)));
+                }
+                numCategories += numCats + 1; // one for round-parent
+                var newtr = TaxonomyReader.OpenIfChanged(reader);
+                Assert.NotNull(newtr);
+                reader.Dispose();
+                reader = newtr;
+
+                // assert categories
+                Assert.AreEqual(numCategories, reader.Size);
+                int roundOrdinal = reader.GetOrdinal(new FacetLabel(Convert.ToString(i)));
+                int[] parents = reader.ParallelTaxonomyArrays.Parents();
+                Assert.AreEqual(0, parents[roundOrdinal]); // round's parent is root
+                for (int j = 0; j < numCats; j++)
+                {
+                    int ord = reader.GetOrdinal(new FacetLabel(Convert.ToString(i), Convert.ToString(j)));
+                    Assert.AreEqual(roundOrdinal, parents[ord]); // round's parent is root
+                }
+            }
+
+            reader.Dispose();
+            writer.Dispose();
+            dir.Dispose();
+        }
+
+        private class DirectoryTaxonomyWriterAnonymousInnerClassHelper : DirectoryTaxonomyWriter
+        {
+            private readonly TestDirectoryTaxonomyReader outerInstance;
+
+            public DirectoryTaxonomyWriterAnonymousInnerClassHelper(TestDirectoryTaxonomyReader outerInstance, Directory dir)
+                : base(dir)
+            {
+                this.outerInstance = outerInstance;
+            }
+
+            protected override IndexWriterConfig CreateIndexWriterConfig(IndexWriterConfig.OpenMode_e openMode)
+            {
+                IndexWriterConfig conf = base.CreateIndexWriterConfig(openMode);
+                LogMergePolicy lmp = (LogMergePolicy)conf.MergePolicy;
+                lmp.MergeFactor = 2;
+                return conf;
+            }
+        }
+
+        [Test]
+        public virtual void TestOpenIfChangedMergedSegment()
+        {
+            // test openIfChanged() when all index segments were merged - used to be
+            // a bug in ParentArray, caught by testOpenIfChangedManySegments - only
+            // this test is not random
+            Directory dir = NewDirectory();
+
+            // hold onto IW to forceMerge
+            // note how we don't close it, since DTW will close it.
+            IndexWriter iw = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())).SetMergePolicy(new LogByteSizeMergePolicy()));
+            var writer = new DirectoryTaxonomyWriterAnonymousInnerClassHelper2(this, dir, iw) as DirectoryTaxonomyWriter;
+
+            var reader = new DirectoryTaxonomyReader(writer);
+            Assert.AreEqual(1, reader.Size);
+            Assert.AreEqual(1, reader.ParallelTaxonomyArrays.Parents().Length);
+
+            // add category and call forceMerge -- this should flush IW and merge segments down to 1
+            // in ParentArray.initFromReader, this used to fail assuming there are no parents.
+            writer.AddCategory(new FacetLabel("1"));
+            iw.ForceMerge(1);
+
+            // now calling openIfChanged should trip on the bug
+            var newtr = TaxonomyReader.OpenIfChanged(reader);
+            Assert.NotNull(newtr);
+            reader.Dispose();
+            reader = newtr;
+            Assert.AreEqual(2, reader.Size);
+            Assert.AreEqual(2, reader.ParallelTaxonomyArrays.Parents().Length);
+
+            reader.Dispose();
+            writer.Dispose();
+            dir.Dispose();
+        }
+
+        private class DirectoryTaxonomyWriterAnonymousInnerClassHelper2 : DirectoryTaxonomyWriter
+        {
+            private readonly TestDirectoryTaxonomyReader outerInstance;
+
+            private IndexWriter iw;
+            private IndexWriterConfig config;
+
+            public DirectoryTaxonomyWriterAnonymousInnerClassHelper2(TestDirectoryTaxonomyReader outerInstance, Directory dir, IndexWriter iw) : base(dir)
+            {
+                this.outerInstance = outerInstance;
+                this.iw = iw;
+            }
+
+            protected override IndexWriter OpenIndexWriter(Directory directory, IndexWriterConfig config) 
+            {   
+                return iw;
+            }
+        }
+
+        [Test]
+        public virtual void TestOpenIfChangedNoChangesButSegmentMerges()
+        {
+            // test openIfChanged() when the taxonomy hasn't really changed, but segments
+            // were merged. The NRT reader will be reopened, and ParentArray used to assert
+            // that the new reader contains more ordinals than were given from the old
+            // TaxReader version
+            Directory dir = NewDirectory();
+
+            // hold onto IW to forceMerge
+            // note how we don't close it, since DTW will close it.
+            var iw = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())).SetMergePolicy(new LogByteSizeMergePolicy()));
+            DirectoryTaxonomyWriter writer = new DirectoryTaxonomyWriterAnonymousInnerClassHelper3(this, dir, iw);
+
+
+            // add a category so that the following DTR open will cause a flush and 
+            // a new segment will be created
+            writer.AddCategory(new FacetLabel("a"));
+
+            var reader = new DirectoryTaxonomyReader(writer);
+            Assert.AreEqual(2, reader.Size);
+            Assert.AreEqual(2, reader.ParallelTaxonomyArrays.Parents().Length);
+
+            // merge all the segments so that NRT reader thinks there's a change 
+            iw.ForceMerge(1);
+
+            // now calling openIfChanged should trip on the wrong assert in ParetArray's ctor
+            var newtr = TaxonomyReader.OpenIfChanged(reader);
+            Assert.NotNull(newtr);
+            reader.Dispose();
+            reader = newtr;
+            Assert.AreEqual(2, reader.Size);
+            Assert.AreEqual(2, reader.ParallelTaxonomyArrays.Parents().Length);
+
+            reader.Dispose();
+            writer.Dispose();
+            dir.Dispose();
+        }
+
+        private class DirectoryTaxonomyWriterAnonymousInnerClassHelper3 : DirectoryTaxonomyWriter
+        {
+            private readonly TestDirectoryTaxonomyReader outerInstance;
+
+            private IndexWriter iw;
+
+            public DirectoryTaxonomyWriterAnonymousInnerClassHelper3(TestDirectoryTaxonomyReader outerInstance, Directory dir, IndexWriter iw)
+                : base(dir)
+            {
+                this.outerInstance = outerInstance;
+                this.iw = iw;
+            }
+
+            protected override IndexWriter OpenIndexWriter(Directory directory, IndexWriterConfig config)
+            {
+                return iw;
+            }
+        }
+
+        [Test]
+        public virtual void TestOpenIfChangedReuseAfterRecreate()
+        {
+            // tests that if the taxonomy is recreated, no data is reused from the previous taxonomy
+            Directory dir = NewDirectory();
+            DirectoryTaxonomyWriter writer = new DirectoryTaxonomyWriter(dir);
+            FacetLabel cp_a = new FacetLabel("a");
+            writer.AddCategory(cp_a);
+            writer.Dispose();
+
+            DirectoryTaxonomyReader r1 = new DirectoryTaxonomyReader(dir);
+            // fill r1's caches
+            Assert.AreEqual(1, r1.GetOrdinal(cp_a));
+            Assert.AreEqual(cp_a, r1.GetPath(1));
+
+            // now recreate, add a different category
+            writer = new DirectoryTaxonomyWriter(dir, IndexWriterConfig.OpenMode_e.CREATE);
+            FacetLabel cp_b = new FacetLabel("b");
+            writer.AddCategory(cp_b);
+            writer.Dispose();
+
+            DirectoryTaxonomyReader r2 = TaxonomyReader.OpenIfChanged(r1);
+            Assert.NotNull(r2);
+
+            // fill r2's caches
+            Assert.AreEqual(1, r2.GetOrdinal(cp_b));
+            Assert.AreEqual(cp_b, r2.GetPath(1));
+
+            // check that r1 doesn't see cp_b
+            Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, r1.GetOrdinal(cp_b));
+            Assert.AreEqual(cp_a, r1.GetPath(1));
+
+            // check that r2 doesn't see cp_a
+            Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, r2.GetOrdinal(cp_a));
+            Assert.AreEqual(cp_b, r2.GetPath(1));
+
+            (r2).Dispose();
+            (r1).Dispose();
+            dir.Dispose();
+        }
+
+        [Test]
+        public virtual void TestOpenIfChangedReuse()
+        {
+            // test the reuse of data from the old DTR instance
+            foreach (bool nrt in new bool[] { false, true })
+            {
+                Directory dir = NewDirectory();
+                DirectoryTaxonomyWriter writer = new DirectoryTaxonomyWriter(dir);
+
+                FacetLabel cp_a = new FacetLabel("a");
+                writer.AddCategory(cp_a);
+                if (!nrt)
+                {
+                    writer.Commit();
+                }
+
+                DirectoryTaxonomyReader r1 = nrt ? new DirectoryTaxonomyReader(writer) : new DirectoryTaxonomyReader(dir);
+                // fill r1's caches
+                Assert.AreEqual(1, r1.GetOrdinal(cp_a));
+                Assert.AreEqual(cp_a, r1.GetPath(1));
+
+                FacetLabel cp_b = new FacetLabel("b");
+                writer.AddCategory(cp_b);
+                if (!nrt)
+                {
+                    writer.Commit();
+                }
+
+                DirectoryTaxonomyReader r2 = TaxonomyReader.OpenIfChanged(r1);
+                Assert.NotNull(r2);
+
+                // add r2's categories to the caches
+                Assert.AreEqual(2, r2.GetOrdinal(cp_b));
+                Assert.AreEqual(cp_b, r2.GetPath(2));
+
+                // check that r1 doesn't see cp_b
+                Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, r1.GetOrdinal(cp_b));
+                Assert.Null(r1.GetPath(2));
+
+                (r1).Dispose();
+                (r2).Dispose();
+                writer.Dispose();
+                dir.Dispose();
+            }
+        }
+
+        [Test]
+        public virtual void TestOpenIfChangedReplaceTaxonomy()
+        {
+            // test openIfChanged when replaceTaxonomy is called, which is equivalent to recreate
+            // only can work with NRT as well
+            Directory src = NewDirectory();
+            DirectoryTaxonomyWriter w = new DirectoryTaxonomyWriter(src);
+            FacetLabel cp_b = new FacetLabel("b");
+            w.AddCategory(cp_b);
+            w.Dispose();
+
+            foreach (bool nrt in new bool[] { false, true })
+            {
+                Directory dir = NewDirectory();
+                var writer = new DirectoryTaxonomyWriter(dir);
+
+                FacetLabel cp_a = new FacetLabel("a");
+                writer.AddCategory(cp_a);
+                if (!nrt)
+                {
+                    writer.Commit();
+                }
+
+                DirectoryTaxonomyReader r1 = nrt ? new DirectoryTaxonomyReader(writer) : new DirectoryTaxonomyReader(dir);
+                // fill r1's caches
+                Assert.AreEqual(1, r1.GetOrdinal(cp_a));
+                Assert.AreEqual(cp_a, r1.GetPath(1));
+
+                // now replace taxonomy
+                writer.ReplaceTaxonomy(src);
+                if (!nrt)
+                {
+                    writer.Commit();
+                }
+
+                DirectoryTaxonomyReader r2 = TaxonomyReader.OpenIfChanged(r1);
+                Assert.NotNull(r2);
+
+                // fill r2's caches
+                Assert.AreEqual(1, r2.GetOrdinal(cp_b));
+                Assert.AreEqual(cp_b, r2.GetPath(1));
+
+                // check that r1 doesn't see cp_b
+                Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, r1.GetOrdinal(cp_b));
+                Assert.AreEqual(cp_a, r1.GetPath(1));
+
+                // check that r2 doesn't see cp_a
+                Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, r2.GetOrdinal(cp_a));
+                Assert.AreEqual(cp_b, r2.GetPath(1));
+
+                (r2).Dispose();
+                (r1).Dispose();
+                writer.Dispose();
+                dir.Dispose();
+            }
+
+            src.Dispose();
+        }
+
+        [Test]
+        public virtual void TestGetChildren()
+        {
+            Directory dir = NewDirectory();
+            var taxoWriter = new DirectoryTaxonomyWriter(dir);
+            int numCategories = AtLeast(10);
+            int numA = 0, numB = 0;
+            Random random = Random();
+            // add the two categories for which we'll also add children (so asserts are simpler)
+            taxoWriter.AddCategory(new FacetLabel("a"));
+            taxoWriter.AddCategory(new FacetLabel("b"));
+            for (int i = 0; i < numCategories; i++)
+            {
+                if (random.NextBoolean())
+                {
+                    taxoWriter.AddCategory(new FacetLabel("a", Convert.ToString(i)));
+                    ++numA;
+                }
+                else
+                {
+                    taxoWriter.AddCategory(new FacetLabel("b", Convert.ToString(i)));
+                    ++numB;
+                }
+            }
+            // add category with no children
+            taxoWriter.AddCategory(new FacetLabel("c"));
+            taxoWriter.Dispose();
+
+            var taxoReader = new DirectoryTaxonomyReader(dir);
+
+            // non existing category
+            TaxonomyReader.ChildrenIterator it = taxoReader.GetChildren(taxoReader.GetOrdinal(new FacetLabel("invalid")));
+            Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, it.Next());
+
+            // a category with no children
+            it = taxoReader.GetChildren(taxoReader.GetOrdinal(new FacetLabel("c")));
+            Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, it.Next());
+
+            // arbitrary negative ordinal
+            it = taxoReader.GetChildren(-2);
+            Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, it.Next());
+
+            // root's children
+            var roots = new HashSet<string>(Arrays.AsList("a", "b", "c"));
+            it = taxoReader.GetChildren(TaxonomyReader.ROOT_ORDINAL);
+            while (roots.Count > 0)
+            {
+                FacetLabel root = taxoReader.GetPath(it.Next());
+                Assert.AreEqual(1, root.length);
+                Assert.True(roots.Remove(root.components[0]));
+            }
+            Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, it.Next());
+
+            for (int i = 0; i < 2; i++)
+            {
+                FacetLabel cp = i == 0 ? new FacetLabel("a") : new FacetLabel("b");
+                int ordinal = taxoReader.GetOrdinal(cp);
+                it = taxoReader.GetChildren(ordinal);
+                int numChildren = 0;
+                int child;
+                while ((child = it.Next()) != TaxonomyReader.INVALID_ORDINAL)
+                {
+                    FacetLabel path = taxoReader.GetPath(child);
+                    Assert.AreEqual(2, path.length);
+                    Assert.AreEqual(path.components[0], i == 0 ? "a" : "b");
+                    ++numChildren;
+                }
+                int expected = i == 0 ? numA : numB;
+                Assert.AreEqual(expected, numChildren, "invalid num children");
+            }
+            taxoReader.Dispose();
+
+            dir.Dispose();
+        }
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestDirectoryTaxonomyWriter.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestDirectoryTaxonomyWriter.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestDirectoryTaxonomyWriter.cs
new file mode 100644
index 0000000..ebf5455
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestDirectoryTaxonomyWriter.cs
@@ -0,0 +1,588 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Concurrent;
+using System.IO;
+using System.Threading;
+using Lucene.Net.Support;
+using NUnit.Framework;
+
+namespace Lucene.Net.Facet.Taxonomy.Directory
+{
+
+
+    using MockAnalyzer = Lucene.Net.Analysis.MockAnalyzer;
+    using Document = Lucene.Net.Documents.Document;
+    using MemoryOrdinalMap = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter.MemoryOrdinalMap;
+    using TaxonomyWriterCache = Lucene.Net.Facet.Taxonomy.WriterCache.TaxonomyWriterCache;
+    using Cl2oTaxonomyWriterCache = Lucene.Net.Facet.Taxonomy.WriterCache.Cl2oTaxonomyWriterCache;
+    using LruTaxonomyWriterCache = Lucene.Net.Facet.Taxonomy.WriterCache.LruTaxonomyWriterCache;
+    using DirectoryReader = Lucene.Net.Index.DirectoryReader;
+    using IndexReader = Lucene.Net.Index.IndexReader;
+    using IndexWriter = Lucene.Net.Index.IndexWriter;
+    using OpenMode = Lucene.Net.Index.IndexWriterConfig.OpenMode_e;
+    using IndexWriterConfig = Lucene.Net.Index.IndexWriterConfig;
+    using SegmentInfos = Lucene.Net.Index.SegmentInfos;
+    using IndexSearcher = Lucene.Net.Search.IndexSearcher;
+    using AlreadyClosedException = Lucene.Net.Store.AlreadyClosedException;
+    using Directory = Lucene.Net.Store.Directory;
+    using IOUtils = Lucene.Net.Util.IOUtils;
+    using TestUtil = Lucene.Net.Util.TestUtil;
+
+    /*
+     * 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.
+     */
+
+    public class TestDirectoryTaxonomyWriter : FacetTestCase
+    {
+
+        // A No-Op TaxonomyWriterCache which always discards all given categories, and
+        // always returns true in put(), to indicate some cache entries were cleared.
+        private static TaxonomyWriterCache NO_OP_CACHE = new TaxonomyWriterCacheAnonymousInnerClassHelper();
+
+        private class TaxonomyWriterCacheAnonymousInnerClassHelper : TaxonomyWriterCache
+        {
+            public TaxonomyWriterCacheAnonymousInnerClassHelper()
+            {
+            }
+
+
+            public virtual void Close()
+            {
+            }
+            public virtual int Get(FacetLabel categoryPath)
+            {
+                return -1;
+            }
+            public virtual bool Put(FacetLabel categoryPath, int ordinal)
+            {
+                return true;
+            }
+            public virtual bool Full
+            {
+                get
+                {
+                    return true;
+                }
+            }
+            public virtual void Clear()
+            {
+            }
+
+        }
+
+        [Test]
+        public virtual void TestCommit()
+        {
+            // Verifies that nothing is committed to the underlying Directory, if
+            // commit() wasn't called.
+            Directory dir = NewDirectory();
+            var ltw = new DirectoryTaxonomyWriter(dir, OpenMode.CREATE_OR_APPEND, NO_OP_CACHE);
+            Assert.False(DirectoryReader.IndexExists(dir));
+            ltw.Commit(); // first commit, so that an index will be created
+            ltw.AddCategory(new FacetLabel("a"));
+
+            IndexReader r = DirectoryReader.Open(dir);
+            Assert.AreEqual(1, r.NumDocs, "No categories should have been committed to the underlying directory");
+            r.Dispose();
+            ltw.Dispose();
+            dir.Dispose();
+        }
+
+        [Test]
+        public virtual void TestCommitUserData()
+        {
+            // Verifies taxonomy commit data
+            Directory dir = NewDirectory();
+            var taxoWriter = new DirectoryTaxonomyWriter(dir, OpenMode.CREATE_OR_APPEND, NO_OP_CACHE);
+            taxoWriter.AddCategory(new FacetLabel("a"));
+            taxoWriter.AddCategory(new FacetLabel("b"));
+            IDictionary<string, string> userCommitData = new Dictionary<string, string>();
+            userCommitData["testing"] = "1 2 3";
+            taxoWriter.CommitData = userCommitData;
+            taxoWriter.Dispose();
+            var r = DirectoryReader.Open(dir);
+            Assert.AreEqual(3, r.NumDocs, "2 categories plus root should have been committed to the underlying directory");
+            var readUserCommitData = r.IndexCommit.UserData;
+            Assert.True("1 2 3".Equals(readUserCommitData["testing"]), "wrong value extracted from commit data");
+            Assert.NotNull(DirectoryTaxonomyWriter.INDEX_EPOCH + " not found in commitData", readUserCommitData[DirectoryTaxonomyWriter.INDEX_EPOCH]);
+            r.Dispose();
+
+            // open DirTaxoWriter again and commit, INDEX_EPOCH should still exist
+            // in the commit data, otherwise DirTaxoReader.refresh() might not detect
+            // that the taxonomy index has been recreated.
+            taxoWriter = new DirectoryTaxonomyWriter(dir, OpenMode.CREATE_OR_APPEND, NO_OP_CACHE);
+            taxoWriter.AddCategory(new FacetLabel("c")); // add a category so that commit will happen
+
+
+            taxoWriter.CommitData = new Dictionary<string, string>()
+		    {
+			    {"just", "data"}
+		    };
+            taxoWriter.Commit();
+
+            // verify taxoWriter.getCommitData()
+            Assert.NotNull(DirectoryTaxonomyWriter.INDEX_EPOCH + " not found in taoxWriter.commitData", taxoWriter.CommitData[DirectoryTaxonomyWriter.INDEX_EPOCH]);
+            taxoWriter.Dispose();
+
+            r = DirectoryReader.Open(dir);
+            readUserCommitData = r.IndexCommit.UserData;
+            Assert.NotNull(DirectoryTaxonomyWriter.INDEX_EPOCH + " not found in commitData", readUserCommitData[DirectoryTaxonomyWriter.INDEX_EPOCH]);
+            r.Dispose();
+
+            dir.Dispose();
+        }
+
+        [Test]
+        public virtual void TestRollback()
+        {
+            // Verifies that if rollback is called, DTW is closed.
+            Directory dir = NewDirectory();
+            var dtw = new DirectoryTaxonomyWriter(dir);
+            dtw.AddCategory(new FacetLabel("a"));
+            dtw.Rollback();
+            try
+            {
+                dtw.AddCategory(new FacetLabel("a"));
+                Fail("should not have succeeded to add a category following rollback.");
+            }
+            catch (AlreadyClosedException)
+            {
+                // expected
+            }
+
+            dir.Dispose();
+        }
+
+        [Test]
+        public virtual void TestRecreateRollback()
+        {
+            // Tests rollback with OpenMode.CREATE
+            Directory dir = NewDirectory();
+            (new DirectoryTaxonomyWriter(dir)).Dispose();
+            Assert.AreEqual(1, getEpoch(dir));
+            (new DirectoryTaxonomyWriter(dir, OpenMode.CREATE)).Rollback();
+            Assert.AreEqual(1, getEpoch(dir));
+
+            dir.Dispose();
+        }
+
+        [Test]
+        public virtual void TestEnsureOpen()
+        {
+            // verifies that an exception is thrown if DTW was closed
+            Directory dir = NewDirectory();
+            DirectoryTaxonomyWriter dtw = new DirectoryTaxonomyWriter(dir);
+            dtw.Dispose();
+            try
+            {
+                dtw.AddCategory(new FacetLabel("a"));
+                Fail("should not have succeeded to add a category following close.");
+            }
+            catch (AlreadyClosedException)
+            {
+                // expected
+            }
+            dir.Dispose();
+        }
+
+        [Test]
+        private void TouchTaxo(DirectoryTaxonomyWriter taxoWriter, FacetLabel cp)
+        {
+            taxoWriter.AddCategory(cp);
+            taxoWriter.CommitData = new Dictionary<string, string>()
+		{
+			{"just", "data"}
+		};
+            taxoWriter.Commit();
+        }
+
+        [Test]
+        public virtual void TestRecreateAndRefresh()
+        {
+            // DirTaxoWriter lost the INDEX_EPOCH property if it was opened in
+            // CREATE_OR_APPEND (or commit(userData) called twice), which could lead to
+            // DirTaxoReader succeeding to refresh().
+            Directory dir = NewDirectory();
+
+            DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(dir, OpenMode.CREATE_OR_APPEND, NO_OP_CACHE);
+            TouchTaxo(taxoWriter, new FacetLabel("a"));
+
+            var taxoReader = new DirectoryTaxonomyReader(dir);
+
+            TouchTaxo(taxoWriter, new FacetLabel("b"));
+
+            var newtr = TaxonomyReader.OpenIfChanged(taxoReader);
+            taxoReader.Dispose();
+            taxoReader = newtr;
+            Assert.AreEqual(1, Convert.ToInt32(taxoReader.CommitUserData[DirectoryTaxonomyWriter.INDEX_EPOCH]));
+
+            // now recreate the taxonomy, and check that the epoch is preserved after opening DirTW again.
+            taxoWriter.Dispose();
+            taxoWriter = new DirectoryTaxonomyWriter(dir, OpenMode.CREATE, NO_OP_CACHE);
+            TouchTaxo(taxoWriter, new FacetLabel("c"));
+            taxoWriter.Dispose();
+
+            taxoWriter = new DirectoryTaxonomyWriter(dir, OpenMode.CREATE_OR_APPEND, NO_OP_CACHE);
+            TouchTaxo(taxoWriter, new FacetLabel("d"));
+            taxoWriter.Dispose();
+
+            newtr = TaxonomyReader.OpenIfChanged(taxoReader);
+            taxoReader.Dispose();
+            taxoReader = newtr;
+            Assert.AreEqual(2, Convert.ToInt32(taxoReader.CommitUserData[DirectoryTaxonomyWriter.INDEX_EPOCH]));
+
+            taxoReader.Dispose();
+            dir.Dispose();
+        }
+
+        [Test]
+        public virtual void TestBackwardsCompatibility()
+        {
+            // tests that if the taxonomy index doesn't have the INDEX_EPOCH
+            // property (supports pre-3.6 indexes), all still works.
+            Directory dir = NewDirectory();
+
+            // create an empty index first, so that DirTaxoWriter initializes indexEpoch to 1.
+            (new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, null))).Dispose();
+
+            var taxoWriter = new DirectoryTaxonomyWriter(dir, OpenMode.CREATE_OR_APPEND, NO_OP_CACHE);
+            taxoWriter.Dispose();
+
+            var taxoReader = new DirectoryTaxonomyReader(dir);
+            Assert.AreEqual(1, Convert.ToInt32(taxoReader.CommitUserData[DirectoryTaxonomyWriter.INDEX_EPOCH]));
+            Assert.Null(TaxonomyReader.OpenIfChanged(taxoReader));
+            (taxoReader).Dispose();
+
+            dir.Dispose();
+        }
+
+        [Test]
+        public virtual void TestConcurrency()
+        {
+            int ncats = AtLeast(100000); // add many categories
+            int range = ncats * 3; // affects the categories selection
+            AtomicInteger numCats = new AtomicInteger(ncats);
+            Directory dir = NewDirectory();
+            var values = new ConcurrentDictionary<string, string>();
+            double d = Random().NextDouble();
+            TaxonomyWriterCache cache;
+            if (d < 0.7)
+            {
+                // this is the fastest, yet most memory consuming
+                cache = new Cl2oTaxonomyWriterCache(1024, 0.15f, 3);
+            }
+            else if (TEST_NIGHTLY && d > 0.98)
+            {
+                // this is the slowest, but tests the writer concurrency when no caching is done.
+                // only pick it during NIGHTLY tests, and even then, with very low chances.
+                cache = NO_OP_CACHE;
+            }
+            else
+            {
+                // this is slower than CL2O, but less memory consuming, and exercises finding categories on disk too.
+                cache = new LruTaxonomyWriterCache(ncats / 10);
+            }
+            if (VERBOSE)
+            {
+                Console.WriteLine("TEST: use cache=" + cache);
+            }
+            var tw = new DirectoryTaxonomyWriter(dir, OpenMode.CREATE, cache);
+            ThreadClass[] addThreads = new ThreadClass[AtLeast(4)];
+            for (int z = 0; z < addThreads.Length; z++)
+            {
+                addThreads[z] = new ThreadAnonymousInnerClassHelper(this, range, numCats, values, tw);
+            }
+
+            foreach (var t in addThreads)
+            {
+                t.Start();
+            }
+            foreach (var t in addThreads)
+            {
+                t.Join();
+            }
+            tw.Dispose();
+
+            DirectoryTaxonomyReader dtr = new DirectoryTaxonomyReader(dir);
+            // +1 for root category
+            if (values.Count + 1 != dtr.Size)
+            {
+                foreach (string value in values.Keys)
+                {
+                    FacetLabel label = new FacetLabel(FacetsConfig.StringToPath(value));
+                    if (dtr.GetOrdinal(label) == -1)
+                    {
+                        Console.WriteLine("FAIL: path=" + label + " not recognized");
+                    }
+                }
+                Fail("mismatch number of categories");
+            }
+
+            int[] parents = dtr.ParallelTaxonomyArrays.Parents();
+            foreach (string cat in values.Keys)
+            {
+                FacetLabel cp = new FacetLabel(FacetsConfig.StringToPath(cat));
+                Assert.True(dtr.GetOrdinal(cp) > 0, "category not found " + cp);
+                int level = cp.length;
+                int parentOrd = 0; // for root, parent is always virtual ROOT (ord=0)
+                FacetLabel path = new FacetLabel();
+                for (int i = 0; i < level; i++)
+                {
+                    path = cp.Subpath(i + 1);
+                    int ord = dtr.GetOrdinal(path);
+                    Assert.AreEqual(parentOrd, parents[ord], "invalid parent for cp=" + path);
+                    parentOrd = ord; // next level should have this parent
+                }
+            }
+
+            IOUtils.Close(dtr, dir);
+        }
+
+        private class ThreadAnonymousInnerClassHelper : ThreadClass
+        {
+            private readonly TestDirectoryTaxonomyWriter outerInstance;
+
+            private int range;
+            private AtomicInteger numCats;
+            private ConcurrentDictionary<string, string> values;
+            private Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter tw;
+
+            public ThreadAnonymousInnerClassHelper(TestDirectoryTaxonomyWriter outerInstance, int range, AtomicInteger numCats, ConcurrentDictionary<string, string> values, Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter tw)
+            {
+                this.outerInstance = outerInstance;
+                this.range = range;
+                this.numCats = numCats;
+                this.values = values;
+                this.tw = tw;
+            }
+
+            public override void Run()
+            {
+                Random random = Random();
+                while (numCats.DecrementAndGet() > 0)
+                {
+                    try
+                    {
+                        int value = random.Next(range);
+                        FacetLabel cp = new FacetLabel(Convert.ToString(value / 1000), Convert.ToString(value / 10000), Convert.ToString(value / 100000), Convert.ToString(value));
+                        int ord = tw.AddCategory(cp);
+                        Assert.True(tw.GetParent(ord) != -1, "invalid parent for ordinal " + ord + ", category " + cp);
+                        string l1 = FacetsConfig.PathToString(cp.components, 1);
+                        string l2 = FacetsConfig.PathToString(cp.components, 2);
+                        string l3 = FacetsConfig.PathToString(cp.components, 3);
+                        string l4 = FacetsConfig.PathToString(cp.components, 4);
+                        values[l1] = l1;
+                        values[l2] = l2;
+                        values[l3] = l3;
+                        values[l4] = l4;
+                    }
+                    catch (IOException e)
+                    {
+                        throw new Exception(e.Message, e);
+                    }
+                }
+            }
+        }
+
+        private long getEpoch(Directory taxoDir)
+        {
+            SegmentInfos infos = new SegmentInfos();
+            infos.Read(taxoDir);
+            return Convert.ToInt64(infos.UserData[DirectoryTaxonomyWriter.INDEX_EPOCH]);
+        }
+
+        [Test]
+        public virtual void TestReplaceTaxonomy()
+        {
+            Directory input = NewDirectory();
+            var taxoWriter = new DirectoryTaxonomyWriter(input);
+            int ordA = taxoWriter.AddCategory(new FacetLabel("a"));
+            taxoWriter.Dispose();
+
+            Directory dir = NewDirectory();
+            taxoWriter = new DirectoryTaxonomyWriter(dir);
+            int ordB = taxoWriter.AddCategory(new FacetLabel("b"));
+            taxoWriter.AddCategory(new FacetLabel("c"));
+            taxoWriter.Commit();
+
+            long origEpoch = getEpoch(dir);
+
+            // replace the taxonomy with the input one
+            taxoWriter.ReplaceTaxonomy(input);
+
+            // LUCENE-4633: make sure that category "a" is not added again in any case
+            taxoWriter.AddTaxonomy(input, new MemoryOrdinalMap());
+            Assert.AreEqual(2, taxoWriter.Size, "no categories should have been added"); // root + 'a'
+            Assert.AreEqual(ordA, taxoWriter.AddCategory(new FacetLabel("a")), "category 'a' received new ordinal?");
+
+            // add the same category again -- it should not receive the same ordinal !
+            int newOrdB = taxoWriter.AddCategory(new FacetLabel("b"));
+            Assert.AreNotSame(ordB, newOrdB, "new ordinal cannot be the original ordinal");
+            Assert.AreEqual(2, newOrdB, "ordinal should have been 2 since only one category was added by replaceTaxonomy");
+
+            taxoWriter.Dispose();
+
+            long newEpoch = getEpoch(dir);
+            Assert.True(origEpoch < newEpoch, "index epoch should have been updated after replaceTaxonomy");
+
+            dir.Dispose();
+            input.Dispose();
+        }
+
+        [Test]
+        public virtual void TestReaderFreshness()
+        {
+            // ensures that the internal index reader is always kept fresh. Previously,
+            // this simple scenario failed, if the cache just evicted the category that
+            // is being added.
+            Directory dir = NewDirectory();
+            DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(dir, OpenMode.CREATE, NO_OP_CACHE);
+            int o1 = taxoWriter.AddCategory(new FacetLabel("a"));
+            int o2 = taxoWriter.AddCategory(new FacetLabel("a"));
+            Assert.True(o1 == o2, "ordinal for same category that is added twice should be the same !");
+            taxoWriter.Dispose();
+            dir.Dispose();
+        }
+
+        [Test]
+        public virtual void TestCommitNoEmptyCommits()
+        {
+            // LUCENE-4972: DTW used to create empty commits even if no changes were made
+            Directory dir = NewDirectory();
+            DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(dir);
+            taxoWriter.AddCategory(new FacetLabel("a"));
+            taxoWriter.Commit();
+
+            long gen1 = SegmentInfos.GetLastCommitGeneration(dir);
+            taxoWriter.Commit();
+            long gen2 = SegmentInfos.GetLastCommitGeneration(dir);
+            Assert.AreEqual(gen1, gen2, "empty commit should not have changed the index");
+
+            taxoWriter.Dispose();
+            dir.Dispose();
+        }
+
+        [Test]
+        public virtual void TestCloseNoEmptyCommits()
+        {
+            // LUCENE-4972: DTW used to create empty commits even if no changes were made
+            Directory dir = NewDirectory();
+            DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(dir);
+            taxoWriter.AddCategory(new FacetLabel("a"));
+            taxoWriter.Commit();
+
+            long gen1 = SegmentInfos.GetLastCommitGeneration(dir);
+            taxoWriter.Dispose();
+            long gen2 = SegmentInfos.GetLastCommitGeneration(dir);
+            Assert.AreEqual(gen1, gen2, "empty commit should not have changed the index");
+
+            taxoWriter.Dispose();
+            dir.Dispose();
+        }
+
+        [Test]
+        public virtual void TestPrepareCommitNoEmptyCommits()
+        {
+            // LUCENE-4972: DTW used to create empty commits even if no changes were made
+            Directory dir = NewDirectory();
+            DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(dir);
+            taxoWriter.AddCategory(new FacetLabel("a"));
+            taxoWriter.PrepareCommit();
+            taxoWriter.Commit();
+
+            long gen1 = SegmentInfos.GetLastCommitGeneration(dir);
+            taxoWriter.PrepareCommit();
+            taxoWriter.Commit();
+            long gen2 = SegmentInfos.GetLastCommitGeneration(dir);
+            Assert.AreEqual(gen1, gen2, "empty commit should not have changed the index");
+
+            taxoWriter.Dispose();
+            dir.Dispose();
+        }
+
+        [Test]
+        public virtual void TestHugeLabel()
+        {
+            Directory indexDir = NewDirectory(), taxoDir = NewDirectory();
+            IndexWriter indexWriter = new IndexWriter(indexDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())));
+            DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir, OpenMode.CREATE, new Cl2oTaxonomyWriterCache(2, 1f, 1));
+            FacetsConfig config = new FacetsConfig();
+
+            // Add one huge label:
+            string bigs = null;
+            int ordinal = -1;
+
+            int len = FacetLabel.MAX_CATEGORY_PATH_LENGTH - 4; // for the dimension and separator
+            bigs = TestUtil.RandomSimpleString(Random(), len, len);
+            FacetField ff = new FacetField("dim", bigs);
+            FacetLabel cp = new FacetLabel("dim", bigs);
+            ordinal = taxoWriter.AddCategory(cp);
+            Document doc = new Document();
+            doc.Add(ff);
+            indexWriter.AddDocument(config.Build(taxoWriter, doc));
+
+            // Add tiny ones to cause a re-hash
+            for (int i = 0; i < 3; i++)
+            {
+                string s = TestUtil.RandomSimpleString(Random(), 1, 10);
+                taxoWriter.AddCategory(new FacetLabel("dim", s));
+                doc = new Document();
+                doc.Add(new FacetField("dim", s));
+                indexWriter.AddDocument(config.Build(taxoWriter, doc));
+            }
+
+            // when too large components were allowed to be added, this resulted in a new added category
+            Assert.AreEqual(ordinal, taxoWriter.AddCategory(cp));
+
+            IOUtils.Close(indexWriter, taxoWriter);
+
+            DirectoryReader indexReader = DirectoryReader.Open(indexDir);
+            var taxoReader = new DirectoryTaxonomyReader(taxoDir);
+            IndexSearcher searcher = new IndexSearcher(indexReader);
+            DrillDownQuery ddq = new DrillDownQuery(new FacetsConfig());
+            ddq.Add("dim", bigs);
+            Assert.AreEqual(1, searcher.Search(ddq, 10).TotalHits);
+
+            IOUtils.Close(indexReader, taxoReader, indexDir, taxoDir);
+        }
+
+        [Test]
+        public virtual void TestReplaceTaxoWithLargeTaxonomy()
+        {
+            var srcTaxoDir = NewDirectory();
+            var targetTaxoDir = NewDirectory();
+
+            // build source, large, taxonomy
+            DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(srcTaxoDir);
+            int ord = taxoWriter.AddCategory(new FacetLabel("A", "1", "1", "1", "1", "1", "1"));
+            taxoWriter.Dispose();
+
+            taxoWriter = new DirectoryTaxonomyWriter(targetTaxoDir);
+            int ordinal = taxoWriter.AddCategory(new FacetLabel("B", "1"));
+            Assert.AreEqual(1, taxoWriter.GetParent(ordinal)); // call getParent to initialize taxoArrays
+            taxoWriter.Commit();
+
+            taxoWriter.ReplaceTaxonomy(srcTaxoDir);
+            Assert.AreEqual(ord - 1, taxoWriter.GetParent(ord));
+            taxoWriter.Dispose();
+
+            srcTaxoDir.Dispose();
+            targetTaxoDir.Dispose();
+        }
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestCachedOrdinalsReader.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestCachedOrdinalsReader.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestCachedOrdinalsReader.cs
new file mode 100644
index 0000000..d528276
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestCachedOrdinalsReader.cs
@@ -0,0 +1,116 @@
+using System;
+using System.IO;
+using System.Threading;
+using Lucene.Net.Support;
+using NUnit.Framework;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * 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 MockAnalyzer = Lucene.Net.Analysis.MockAnalyzer;
+    using Document = Lucene.Net.Documents.Document;
+    using DirectoryTaxonomyWriter = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter;
+    using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext;
+    using DirectoryReader = Lucene.Net.Index.DirectoryReader;
+    using IndexWriter = Lucene.Net.Index.IndexWriter;
+    using IndexWriterConfig = Lucene.Net.Index.IndexWriterConfig;
+    using Directory = Lucene.Net.Store.Directory;
+    using IOUtils = Lucene.Net.Util.IOUtils;
+    [TestFixture]
+    public class TestCachedOrdinalsReader : FacetTestCase
+    {
+
+        [Test]
+        public virtual void TestWithThreads()
+        {
+            // LUCENE-5303: OrdinalsCache used the ThreadLocal BinaryDV instead of reader.getCoreCacheKey().
+            Store.Directory indexDir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+            IndexWriterConfig conf = NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random()));
+            IndexWriter writer = new IndexWriter(indexDir, conf);
+            var taxoWriter = new DirectoryTaxonomyWriter(taxoDir);
+            FacetsConfig config = new FacetsConfig();
+
+            Document doc = new Document();
+            doc.Add(new FacetField("A", "1"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+            doc = new Document();
+            doc.Add(new FacetField("A", "2"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            var reader = DirectoryReader.Open(writer, true);
+            CachedOrdinalsReader ordsReader = new CachedOrdinalsReader(new DocValuesOrdinalsReader(FacetsConfig.DEFAULT_INDEX_FIELD_NAME));
+            ThreadClass[] threads = new ThreadClass[3];
+            for (int i = 0; i < threads.Length; i++)
+            {
+                threads[i] = new ThreadAnonymousInnerClassHelper(this, "CachedOrdsThread-" + i, reader, ordsReader);
+            }
+
+            long ramBytesUsed = 0;
+            foreach (ThreadClass t in threads)
+            {
+                t.Start();
+                t.Join();
+                if (ramBytesUsed == 0)
+                {
+                    ramBytesUsed = ordsReader.RamBytesUsed();
+                }
+                else
+                {
+                    Assert.AreEqual(ramBytesUsed, ordsReader.RamBytesUsed());
+                }
+            }
+
+            IOUtils.Close(writer, taxoWriter, reader, indexDir, taxoDir);
+        }
+
+        private class ThreadAnonymousInnerClassHelper : ThreadClass
+        {
+            private readonly TestCachedOrdinalsReader outerInstance;
+
+            private DirectoryReader reader;
+            private Lucene.Net.Facet.Taxonomy.CachedOrdinalsReader ordsReader;
+
+            public ThreadAnonymousInnerClassHelper(TestCachedOrdinalsReader outerInstance, string CachedOrdsThread, DirectoryReader reader, Lucene.Net.Facet.Taxonomy.CachedOrdinalsReader ordsReader)
+                : base("CachedOrdsThread-")
+            {
+                this.outerInstance = outerInstance;
+                this.reader = reader;
+                this.ordsReader = ordsReader;
+            }
+
+            public override void Run()
+            {
+                foreach (var context in reader.Leaves)
+                {
+                    try
+                    {
+                        ordsReader.GetReader(context);
+                    }
+                    catch (IOException e)
+                    {
+                        throw new Exception(e.Message, e);
+                    }
+                }
+            }
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestFacetLabel.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestFacetLabel.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestFacetLabel.cs
new file mode 100644
index 0000000..7f09146
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestFacetLabel.cs
@@ -0,0 +1,360 @@
+using System;
+using Lucene.Net.Support;
+using NUnit.Framework;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    using SortedSetDocValuesFacetField = Lucene.Net.Facet.SortedSet.SortedSetDocValuesFacetField;
+    using BytesRef = Lucene.Net.Util.BytesRef;
+    using TestUtil = Lucene.Net.Util.TestUtil;
+
+    /*
+     * 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.
+     */
+    [TestFixture]
+    public class TestFacetLabel : FacetTestCase
+    {
+
+        [Test]
+        public virtual void TestBasic()
+        {
+            Assert.AreEqual(0, (new FacetLabel()).length);
+            Assert.AreEqual(1, (new FacetLabel("hello")).length);
+            Assert.AreEqual(2, (new FacetLabel("hello", "world")).length);
+        }
+
+        [Test]
+        public virtual void TestToString()
+        {
+            // When the category is empty, we expect an empty string
+            Assert.AreEqual("FacetLabel: []", (new FacetLabel()).ToString());
+            // one category
+            Assert.AreEqual("FacetLabel: [hello]", (new FacetLabel("hello")).ToString());
+            // more than one category
+            Assert.AreEqual("FacetLabel: [hello, world]", (new FacetLabel("hello", "world")).ToString());
+        }
+
+        [Test]
+        public virtual void TestGetComponent()
+        {
+            string[] components = new string[AtLeast(10)];
+            for (int i = 0; i < components.Length; i++)
+            {
+                components[i] = Convert.ToString(i);
+            }
+            FacetLabel cp = new FacetLabel(components);
+            for (int i = 0; i < components.Length; i++)
+            {
+                Assert.AreEqual(i, Convert.ToInt32(cp.components[i]));
+            }
+        }
+
+        [Test]
+        public virtual void TestDefaultConstructor()
+        {
+            // test that the default constructor (no parameters) currently
+            // defaults to creating an object with a 0 initial capacity.
+            // If we change this default later, we also need to change this
+            // test.
+            FacetLabel p = new FacetLabel();
+            Assert.AreEqual(0, p.length);
+            Assert.AreEqual("FacetLabel: []", p.ToString());
+        }
+
+        [Test]
+        public virtual void TestSubPath()
+        {
+            FacetLabel p = new FacetLabel("hi", "there", "man");
+            Assert.AreEqual(p.length, 3);
+
+            FacetLabel p1 = p.Subpath(2);
+            Assert.AreEqual(2, p1.length);
+            Assert.AreEqual("FacetLabel: [hi, there]", p1.ToString());
+
+            p1 = p.Subpath(1);
+            Assert.AreEqual(1, p1.length);
+            Assert.AreEqual("FacetLabel: [hi]", p1.ToString());
+
+            p1 = p.Subpath(0);
+            Assert.AreEqual(0, p1.length);
+            Assert.AreEqual("FacetLabel: []", p1.ToString());
+
+            // with all the following lengths, the prefix should be the whole path 
+            int[] lengths = new int[] { 3, -1, 4 };
+            for (int i = 0; i < lengths.Length; i++)
+            {
+                p1 = p.Subpath(lengths[i]);
+                Assert.AreEqual(3, p1.length);
+                Assert.AreEqual("FacetLabel: [hi, there, man]", p1.ToString());
+                Assert.AreEqual(p, p1);
+            }
+        }
+
+        [Test]
+        public virtual void TestEquals()
+        {
+            Assert.AreEqual(new FacetLabel(), new FacetLabel());
+            Assert.False((new FacetLabel()).Equals(new FacetLabel("hi")));
+            Assert.False((new FacetLabel()).Equals(Convert.ToInt32(3)));
+            Assert.AreEqual(new FacetLabel("hello", "world"), new FacetLabel("hello", "world"));
+        }
+
+        [Test]
+        public virtual void TestHashCode()
+        {
+            Assert.AreEqual((new FacetLabel()).GetHashCode(), (new FacetLabel()).GetHashCode());
+            Assert.False((new FacetLabel()).GetHashCode() == (new FacetLabel("hi")).GetHashCode());
+            Assert.AreEqual((new FacetLabel("hello", "world")).GetHashCode(), (new FacetLabel("hello", "world")).GetHashCode());
+        }
+
+        [Test]
+        public virtual void TestLongHashCode()
+        {
+            Assert.AreEqual((new FacetLabel()).LongHashCode(), (new FacetLabel()).LongHashCode());
+            Assert.False((new FacetLabel()).LongHashCode() == (new FacetLabel("hi")).LongHashCode());
+            Assert.AreEqual((new FacetLabel("hello", "world")).LongHashCode(), (new FacetLabel("hello", "world")).LongHashCode());
+        }
+
+        [Test]
+        public virtual void TestArrayConstructor()
+        {
+            FacetLabel p = new FacetLabel("hello", "world", "yo");
+            Assert.AreEqual(3, p.length);
+            Assert.AreEqual("FacetLabel: [hello, world, yo]", p.ToString());
+        }
+
+        [Test]
+        public virtual void TestCompareTo()
+        {
+            FacetLabel p = new FacetLabel("a", "b", "c", "d");
+            FacetLabel pother = new FacetLabel("a", "b", "c", "d");
+            Assert.AreEqual(0, pother.CompareTo(p));
+            Assert.AreEqual(0, p.CompareTo(pother));
+            pother = new FacetLabel();
+            Assert.True(pother.CompareTo(p) < 0);
+            Assert.True(p.CompareTo(pother) > 0);
+            pother = new FacetLabel("a", "b_", "c", "d");
+            Assert.True(pother.CompareTo(p) > 0);
+            Assert.True(p.CompareTo(pother) < 0);
+            pother = new FacetLabel("a", "b", "c");
+            Assert.True(pother.CompareTo(p) < 0);
+            Assert.True(p.CompareTo(pother) > 0);
+            pother = new FacetLabel("a", "b", "c", "e");
+            Assert.True(pother.CompareTo(p) > 0);
+            Assert.True(p.CompareTo(pother) < 0);
+        }
+
+        [Test]
+        public virtual void TestEmptyNullComponents()
+        {
+            // LUCENE-4724: CategoryPath should not allow empty or null components
+            string[][] components_tests = new string[][]
+		{
+			new string[] {"", "test"},
+			new string[] {"test", ""},
+			new string[] {"test", "", "foo"},
+			new string[] {null, "test"},
+			new string[] {"test", null},
+			new string[] {"test", null, "foo"}
+		};
+
+            foreach (string[] components in components_tests)
+            {
+                try
+                {
+                    Assert.NotNull(new FacetLabel(components));
+                    Fail("empty or null components should not be allowed: " + Arrays.ToString(components));
+                }
+                catch (System.ArgumentException)
+                {
+                    // expected
+                }
+                try
+                {
+                    new FacetField("dim", components);
+                    Fail("empty or null components should not be allowed: " + Arrays.ToString(components));
+                }
+                catch (System.ArgumentException)
+                {
+                    // expected
+                }
+                try
+                {
+                    new AssociationFacetField(new BytesRef(), "dim", components);
+                    Fail("empty or null components should not be allowed: " + Arrays.ToString(components));
+                }
+                catch (System.ArgumentException)
+                {
+                    // expected
+                }
+                try
+                {
+                    new IntAssociationFacetField(17, "dim", components);
+                    Fail("empty or null components should not be allowed: " + Arrays.ToString(components));
+                }
+                catch (System.ArgumentException)
+                {
+                    // expected
+                }
+                try
+                {
+                    new FloatAssociationFacetField(17.0f, "dim", components);
+                    Fail("empty or null components should not be allowed: " + Arrays.ToString(components));
+                }
+                catch (System.ArgumentException)
+                {
+                    // expected
+                }
+            }
+            try
+            {
+                new FacetField(null, new string[] { "abc" });
+                Fail("empty or null components should not be allowed");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+            try
+            {
+                new FacetField("", new string[] { "abc" });
+                Fail("empty or null components should not be allowed");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+            try
+            {
+                new IntAssociationFacetField(17, null, new string[] { "abc" });
+                Fail("empty or null components should not be allowed");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+            try
+            {
+                new IntAssociationFacetField(17, "", new string[] { "abc" });
+                Fail("empty or null components should not be allowed");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+            try
+            {
+                new FloatAssociationFacetField(17.0f, null, new string[] { "abc" });
+                Fail("empty or null components should not be allowed");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+            try
+            {
+                new FloatAssociationFacetField(17.0f, "", new string[] { "abc" });
+                Fail("empty or null components should not be allowed");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+            try
+            {
+                new AssociationFacetField(new BytesRef(), null, new string[] { "abc" });
+                Fail("empty or null components should not be allowed");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+            try
+            {
+                new AssociationFacetField(new BytesRef(), "", new string[] { "abc" });
+                Fail("empty or null components should not be allowed");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+            try
+            {
+                new SortedSetDocValuesFacetField(null, "abc");
+                Fail("empty or null components should not be allowed");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+            try
+            {
+                new SortedSetDocValuesFacetField("", "abc");
+                Fail("empty or null components should not be allowed");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+            try
+            {
+                new SortedSetDocValuesFacetField("dim", null);
+                Fail("empty or null components should not be allowed");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+            try
+            {
+                new SortedSetDocValuesFacetField("dim", "");
+                Fail("empty or null components should not be allowed");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+        }
+
+        [Test]
+        public virtual void TestLongPath()
+        {
+            string bigComp = null;
+            while (true)
+            {
+                int len = FacetLabel.MAX_CATEGORY_PATH_LENGTH;
+                bigComp = TestUtil.RandomSimpleString(Random(), len, len);
+                if (bigComp.IndexOf('\u001f') != -1)
+                {
+                    continue;
+                }
+                break;
+            }
+
+            try
+            {
+                Assert.NotNull(new FacetLabel("dim", bigComp));
+                Fail("long paths should not be allowed; len=" + bigComp.Length);
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestLRUHashMap.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestLRUHashMap.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestLRUHashMap.cs
new file mode 100644
index 0000000..71b62c5
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestLRUHashMap.cs
@@ -0,0 +1,65 @@
+using NUnit.Framework;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * 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 Lucene.Net.Facet.Taxonomy;
+    [TestFixture]
+    public class TestLRUHashMap : FacetTestCase
+    {
+        // testLRU() tests that the specified size limit is indeed honored, and
+        // the remaining objects in the map are indeed those that have been most
+        // recently used
+        [Test]
+        public virtual void TestLru()
+        {
+            LRUHashMap<string, string> lru = new LRUHashMap<string, string>(3,1);
+            Assert.AreEqual(0, lru.Size());
+            lru.Put("one", "Hello world");
+            Assert.AreEqual(1, lru.Size());
+            lru.Put("two", "Hi man");
+            Assert.AreEqual(2, lru.Size());
+            lru.Put("three", "Bonjour");
+            Assert.AreEqual(3, lru.Size());
+            lru.Put("four", "Shalom");
+            Assert.AreEqual(3, lru.Size());
+            Assert.NotNull(lru.Get("three"));
+            Assert.NotNull(lru.Get("two"));
+            Assert.NotNull(lru.Get("four"));
+            Assert.Null(lru.Get("one"));
+            lru.Put("five", "Yo!");
+            Assert.AreEqual(3, lru.Size());
+            Assert.Null(lru.Get("three")); // three was last used, so it got removed
+            Assert.NotNull(lru.Get("five"));
+            lru.Get("four");
+            lru.Put("six", "hi");
+            lru.Put("seven", "hey dude");
+            Assert.AreEqual(3, lru.Size());
+            Assert.Null(lru.Get("one"));
+            Assert.Null(lru.Get("two"));
+            Assert.Null(lru.Get("three"));
+            Assert.NotNull(lru.Get("four"));
+            Assert.Null(lru.Get("five"));
+            Assert.NotNull(lru.Get("six"));
+            Assert.NotNull(lru.Get("seven"));
+        }
+    }
+
+}
\ No newline at end of file


[03/26] lucenenet git commit: fix Equals for Query object

Posted by sy...@apache.org.
fix Equals for Query object


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

Branch: refs/heads/master
Commit: ca463d34051f3abac7c4ae4f104a6af03c1e14f9
Parents: 9971717
Author: eladmarg <el...@gmail.com>
Authored: Sat Nov 8 17:37:12 2014 -0800
Committer: eladmarg <el...@gmail.com>
Committed: Sat Nov 8 17:37:12 2014 -0800

----------------------------------------------------------------------
 src/Lucene.Net.Core/Search/Query.cs | 8 ++++++++
 1 file changed, 8 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucenenet/blob/ca463d34/src/Lucene.Net.Core/Search/Query.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Search/Query.cs b/src/Lucene.Net.Core/Search/Query.cs
index 3a66bd3..5a32b5b 100644
--- a/src/Lucene.Net.Core/Search/Query.cs
+++ b/src/Lucene.Net.Core/Search/Query.cs
@@ -133,6 +133,14 @@ namespace Lucene.Net.Search
             {
                 return true;
             }
+            if (Object.ReferenceEquals(obj, this))
+            {
+                return true;
+            }
+
+            if (GetType() != obj.GetType())
+                return false;
+
             var other = obj as Query;
             if (other == null)
             {


[16/26] lucenenet git commit: first commit of facet porting, failing tests will be fixed in next commits.

Posted by sy...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Range/LongRange.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Range/LongRange.cs b/Lucene.Net.Facet/Range/LongRange.cs
new file mode 100644
index 0000000..52da204
--- /dev/null
+++ b/Lucene.Net.Facet/Range/LongRange.cs
@@ -0,0 +1,239 @@
+using System.Collections.Generic;
+using Lucene.Net.Support;
+
+namespace Lucene.Net.Facet.Range
+{
+
+    /*
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements.  See the NOTICE file distributed with
+     * this work for additional information regarding copyright ownership.
+     * The ASF licenses this file to You under the Apache License, Version 2.0
+     * (the "License"); you may not use this file except in compliance with
+     * the License.  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     */
+
+
+    using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext;
+    using FunctionValues = Lucene.Net.Queries.Function.FunctionValues;
+    using ValueSource = Lucene.Net.Queries.Function.ValueSource;
+    using DocIdSet = Lucene.Net.Search.DocIdSet;
+    using DocIdSetIterator = Lucene.Net.Search.DocIdSetIterator;
+    using Filter = Lucene.Net.Search.Filter;
+    using Bits = Lucene.Net.Util.Bits;
+
+    /// <summary>
+    /// Represents a range over long values.
+    /// 
+    /// @lucene.experimental 
+    /// </summary>
+    public sealed class LongRange : Range
+    {
+        internal readonly long minIncl;
+        internal readonly long maxIncl;
+
+        /// <summary>
+        /// Minimum. </summary>
+        public readonly long min;
+
+        /// <summary>
+        /// Maximum. </summary>
+        public readonly long max;
+
+        /// <summary>
+        /// True if the minimum value is inclusive. </summary>
+        public readonly bool minInclusive;
+
+        /// <summary>
+        /// True if the maximum value is inclusive. </summary>
+        public readonly bool maxInclusive;
+
+        // TODO: can we require fewer args? (same for
+        // Double/FloatRange too)
+
+        /// <summary>
+        /// Create a LongRange. </summary>
+        public LongRange(string label, long minIn, bool minInclusive, long maxIn, bool maxInclusive)
+            : base(label)
+        {
+            this.min = minIn;
+            this.max = maxIn;
+            this.minInclusive = minInclusive;
+            this.maxInclusive = maxInclusive;
+
+            if (!minInclusive)
+            {
+                if (minIn != long.MaxValue)
+                {
+                    minIn++;
+                }
+                else
+                {
+                    FailNoMatch();
+                }
+            }
+
+            if (!maxInclusive)
+            {
+                if (maxIn != long.MinValue)
+                {
+                    maxIn--;
+                }
+                else
+                {
+                    FailNoMatch();
+                }
+            }
+
+            if (minIn > maxIn)
+            {
+                FailNoMatch();
+            }
+
+            this.minIncl = minIn;
+            this.maxIncl = maxIn;
+        }
+
+        /// <summary>
+        /// True if this range accepts the provided value. </summary>
+        public bool accept(long value)
+        {
+            return value >= minIncl && value <= maxIncl;
+        }
+
+        public override string ToString()
+        {
+            return "LongRange(" + minIncl + " to " + maxIncl + ")";
+        }
+
+        public override Filter GetFilter(Filter fastMatchFilter, ValueSource valueSource)
+        {
+            return new FilterAnonymousInnerClassHelper(this, fastMatchFilter, valueSource);
+        }
+
+        private class FilterAnonymousInnerClassHelper : Filter
+        {
+            private readonly LongRange outerInstance;
+
+            private Filter fastMatchFilter;
+            private ValueSource valueSource;
+
+            public FilterAnonymousInnerClassHelper(LongRange outerInstance, Filter fastMatchFilter, ValueSource valueSource)
+            {
+                this.outerInstance = outerInstance;
+                this.fastMatchFilter = fastMatchFilter;
+                this.valueSource = valueSource;
+            }
+
+
+            public override string ToString()
+            {
+                return "Filter(" + outerInstance.ToString() + ")";
+            }
+
+            public override DocIdSet GetDocIdSet(AtomicReaderContext context, Bits acceptDocs)
+            {
+
+                // TODO: this is just like ValueSourceScorer,
+                // ValueSourceFilter (spatial),
+                // ValueSourceRangeFilter (solr); also,
+                // https://issues.apache.org/jira/browse/LUCENE-4251
+
+                FunctionValues values = valueSource.GetValues(new Dictionary<string,object>(), context);
+
+                int maxDoc = context.Reader.MaxDoc;
+
+                Bits fastMatchBits;
+                if (fastMatchFilter != null)
+                {
+                    DocIdSet dis = fastMatchFilter.GetDocIdSet(context, null);
+                    if (dis == null)
+                    {
+                        // No documents match
+                        return null;
+                    }
+                    fastMatchBits = dis.GetBits();
+                    if (fastMatchBits == null)
+                    {
+                        throw new System.ArgumentException("fastMatchFilter does not implement DocIdSet.bits");
+                    }
+                }
+                else
+                {
+                    fastMatchBits = null;
+                }
+
+                return new DocIdSetAnonymousInnerClassHelper(this, acceptDocs, values, maxDoc, fastMatchBits);
+            }
+
+            private class DocIdSetAnonymousInnerClassHelper : DocIdSet
+            {
+                private readonly FilterAnonymousInnerClassHelper outerInstance;
+
+                private Bits acceptDocs;
+                private FunctionValues values;
+                private int maxDoc;
+                private Bits fastMatchBits;
+
+                public DocIdSetAnonymousInnerClassHelper(FilterAnonymousInnerClassHelper outerInstance, Bits acceptDocs, FunctionValues values, int maxDoc, Bits fastMatchBits)
+                {
+                    this.outerInstance = outerInstance;
+                    this.acceptDocs = acceptDocs;
+                    this.values = values;
+                    this.maxDoc = maxDoc;
+                    this.fastMatchBits = fastMatchBits;
+                }
+
+
+                public override Bits GetBits()
+                {
+                    return new BitsAnonymousInnerClassHelper(this);
+                }
+
+                private class BitsAnonymousInnerClassHelper : Bits
+                {
+                    private readonly DocIdSetAnonymousInnerClassHelper outerInstance;
+
+                    public BitsAnonymousInnerClassHelper(DocIdSetAnonymousInnerClassHelper outerInstance)
+                    {
+                        this.outerInstance = outerInstance;
+                    }
+
+                    public virtual bool Get(int docID)
+                    {
+                        if (outerInstance.acceptDocs != null && outerInstance.acceptDocs.Get(docID) == false)
+                        {
+                            return false;
+                        }
+                        if (outerInstance.fastMatchBits != null && outerInstance.fastMatchBits.Get(docID) == false)
+                        {
+                            return false;
+                        }
+                        return outerInstance.outerInstance.outerInstance.accept(outerInstance.values.LongVal(docID));
+                    }
+
+                    
+                    public virtual int Length()
+                    {
+                        return outerInstance.maxDoc;
+                    }
+                }
+
+                public override DocIdSetIterator GetIterator()
+                {
+                    throw new System.NotSupportedException("this filter can only be accessed via bits()");
+                }
+
+            }
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Range/LongRangeCounter.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Range/LongRangeCounter.cs b/Lucene.Net.Facet/Range/LongRangeCounter.cs
new file mode 100644
index 0000000..98123c2
--- /dev/null
+++ b/Lucene.Net.Facet/Range/LongRangeCounter.cs
@@ -0,0 +1,385 @@
+using System.Diagnostics;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Lucene.Net.Facet.Range
+{
+
+    /*
+     * 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.
+     */
+
+
+    /// <summary>
+    /// Counts how many times each range was seen;
+    ///  per-hit it's just a binary search (<seealso cref="#add"/>)
+    ///  against the elementary intervals, and in the end we
+    ///  rollup back to the original ranges. 
+    /// </summary>
+
+    internal sealed class LongRangeCounter
+    {
+
+        internal readonly LongRangeNode root;
+        internal readonly long[] boundaries;
+        internal readonly int[] leafCounts;
+
+        // Used during rollup
+        private int leafUpto;
+        private int missingCount;
+
+        public LongRangeCounter(LongRange[] ranges)
+        {
+            // Maps all range inclusive endpoints to int flags; 1
+            // = start of interval, 2 = end of interval.  We need to
+            // track the start vs end case separately because if a
+            // given point is both, then it must be its own
+            // elementary interval:
+            IDictionary<long?, int?> endsMap = new Dictionary<long?, int?>();
+
+            endsMap[long.MinValue] = 1;
+            endsMap[long.MaxValue] = 2;
+
+            foreach (LongRange range in ranges)
+            {
+                int? cur;
+                if (!endsMap.TryGetValue(range.minIncl,out cur))
+                {
+                    endsMap[range.minIncl] = 1;
+                }
+                else
+                {
+                    endsMap[range.minIncl] = (int)cur | 1;
+                }
+                
+                if (!endsMap.TryGetValue(range.maxIncl,out cur))
+                {
+                    endsMap[range.maxIncl] = 2;
+                }
+                else
+                {
+                    endsMap[range.maxIncl] = (int)cur | 2;
+                }
+            }
+
+            var endsList = new List<long?>(endsMap.Keys);
+            endsList.Sort();
+
+            // Build elementaryIntervals (a 1D Venn diagram):
+            IList<InclusiveRange> elementaryIntervals = new List<InclusiveRange>();
+            int upto0 = 1;
+            long v = endsList[0].HasValue ? endsList[0].Value : 0;
+            long prev;
+            if (endsMap[v] == 3)
+            {
+                elementaryIntervals.Add(new InclusiveRange(v, v));
+                prev = v + 1;
+            }
+            else
+            {
+                prev = v;
+            }
+
+            while (upto0 < endsList.Count)
+            {
+                v = endsList[upto0].HasValue ?  endsList[upto0].Value : 0;
+                int flags = endsMap[v].HasValue ? endsMap[v].Value : 0;
+                //System.out.println("  v=" + v + " flags=" + flags);
+                if (flags == 3)
+                {
+                    // This point is both an end and a start; we need to
+                    // separate it:
+                    if (v > prev)
+                    {
+                        elementaryIntervals.Add(new InclusiveRange(prev, v - 1));
+                    }
+                    elementaryIntervals.Add(new InclusiveRange(v, v));
+                    prev = v + 1;
+                }
+                else if (flags == 1)
+                {
+                    // This point is only the start of an interval;
+                    // attach it to next interval:
+                    if (v > prev)
+                    {
+                        elementaryIntervals.Add(new InclusiveRange(prev, v - 1));
+                    }
+                    prev = v;
+                }
+                else
+                {
+                    Debug.Assert(flags == 2);
+                    // This point is only the end of an interval; attach
+                    // it to last interval:
+                    elementaryIntervals.Add(new InclusiveRange(prev, v));
+                    prev = v + 1;
+                }
+                //System.out.println("    ints=" + elementaryIntervals);
+                upto0++;
+            }
+
+            // Build binary tree on top of intervals:
+            root = Split(0, elementaryIntervals.Count, elementaryIntervals);
+
+            // Set outputs, so we know which range to output for
+            // each node in the tree:
+            for (int i = 0; i < ranges.Length; i++)
+            {
+                root.addOutputs(i, ranges[i]);
+            }
+
+            // Set boundaries (ends of each elementary interval):
+            boundaries = new long[elementaryIntervals.Count];
+            for (int i = 0; i < boundaries.Length; i++)
+            {
+                boundaries[i] = elementaryIntervals[i].end;
+            }
+
+            leafCounts = new int[boundaries.Length];
+
+            //System.out.println("ranges: " + Arrays.toString(ranges));
+            //System.out.println("intervals: " + elementaryIntervals);
+            //System.out.println("boundaries: " + Arrays.toString(boundaries));
+            //System.out.println("root:\n" + root);
+        }
+
+        public void add(long v)
+        {
+            //System.out.println("add v=" + v);
+
+            // NOTE: this works too, but it's ~6% slower on a simple
+            // test with a high-freq TermQuery w/ range faceting on
+            // wikimediumall:
+            /*
+            int index = Arrays.binarySearch(boundaries, v);
+            if (index < 0) {
+              index = -index-1;
+            }
+            leafCounts[index]++;
+            */
+
+            // Binary search to find matched elementary range; we
+            // are guaranteed to find a match because the last
+            // boundary is Long.MAX_VALUE:
+
+            int lo = 0;
+            int hi = boundaries.Length - 1;
+            while (true)
+            {
+                int mid = (int)((uint)(lo + hi) >> 1);
+                //System.out.println("  cycle lo=" + lo + " hi=" + hi + " mid=" + mid + " boundary=" + boundaries[mid] + " to " + boundaries[mid+1]);
+                if (v <= boundaries[mid])
+                {
+                    if (mid == 0)
+                    {
+                        leafCounts[0]++;
+                        return;
+                    }
+                    else
+                    {
+                        hi = mid - 1;
+                    }
+                }
+                else if (v > boundaries[mid + 1])
+                {
+                    lo = mid + 1;
+                }
+                else
+                {
+                    leafCounts[mid + 1]++;
+                    //System.out.println("  incr @ " + (mid+1) + "; now " + leafCounts[mid+1]);
+                    return;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Fills counts corresponding to the original input
+        ///  ranges, returning the missing count (how many hits
+        ///  didn't match any ranges). 
+        /// </summary>
+        public int fillCounts(int[] counts)
+        {
+            //System.out.println("  rollup");
+            missingCount = 0;
+            leafUpto = 0;
+            rollup(root, counts, false);
+            return missingCount;
+        }
+
+        private int rollup(LongRangeNode node, int[] counts, bool sawOutputs)
+        {
+            int count;
+            sawOutputs |= node.outputs != null;
+            if (node.left != null)
+            {
+                count = rollup(node.left, counts, sawOutputs);
+                count += rollup(node.right, counts, sawOutputs);
+            }
+            else
+            {
+                // Leaf:
+                count = leafCounts[leafUpto];
+                leafUpto++;
+                if (!sawOutputs)
+                {
+                    // This is a missing count (no output ranges were
+                    // seen "above" us):
+                    missingCount += count;
+                }
+            }
+            if (node.outputs != null)
+            {
+                foreach (int rangeIndex in node.outputs)
+                {
+                    counts[rangeIndex] += count;
+                }
+            }
+            //System.out.println("  rollup node=" + node.start + " to " + node.end + ": count=" + count);
+            return count;
+        }
+
+        private static LongRangeNode Split(int start, int end, IList<InclusiveRange> elementaryIntervals)
+        {
+            if (start == end - 1)
+            {
+                // leaf
+                InclusiveRange range = elementaryIntervals[start];
+                return new LongRangeNode(range.start, range.end, null, null, start);
+            }
+            else
+            {
+                int mid = (int)((uint)(start + end) >> 1);
+                LongRangeNode left = Split(start, mid, elementaryIntervals);
+                LongRangeNode right = Split(mid, end, elementaryIntervals);
+                return new LongRangeNode(left.start, right.end, left, right, -1);
+            }
+        }
+
+        private sealed class InclusiveRange
+        {
+            public readonly long start;
+            public readonly long end;
+
+            public InclusiveRange(long start, long end)
+            {
+                Debug.Assert(end >= start);
+                this.start = start;
+                this.end = end;
+            }
+
+            public override string ToString()
+            {
+                return start + " to " + end;
+            }
+        }
+
+        /// <summary>
+        /// Holds one node of the segment tree. </summary>
+        public sealed class LongRangeNode
+        {
+            internal readonly LongRangeNode left;
+            internal readonly LongRangeNode right;
+
+            // Our range, inclusive:
+            internal readonly long start;
+            internal readonly long end;
+
+            // If we are a leaf, the index into elementary ranges that
+            // we point to:
+            internal readonly int leafIndex;
+
+            // Which range indices to output when a query goes
+            // through this node:
+            internal IList<int?> outputs;
+
+            public LongRangeNode(long start, long end, LongRangeNode left, LongRangeNode right, int leafIndex)
+            {
+                this.start = start;
+                this.end = end;
+                this.left = left;
+                this.right = right;
+                this.leafIndex = leafIndex;
+            }
+
+            public override string ToString()
+            {
+                StringBuilder sb = new StringBuilder();
+                ToString(sb, 0);
+                return sb.ToString();
+            }
+
+            internal static void indent(StringBuilder sb, int depth)
+            {
+                for (int i = 0; i < depth; i++)
+                {
+                    sb.Append("  ");
+                }
+            }
+
+            /// <summary>
+            /// Recursively assigns range outputs to each node. </summary>
+            internal void addOutputs(int index, LongRange range)
+		{
+		  if (start >= range.minIncl && end <= range.maxIncl)
+		  {
+			// Our range is fully included in the incoming
+			// range; add to our output list:
+			if (outputs == null)
+			{
+			  outputs = new List<int?>();
+			}
+			outputs.Add(index);
+		  }
+		  else if (left != null)
+		  {
+			Debug.Assert(right != null);
+			// Recurse:
+			left.addOutputs(index, range);
+			right.addOutputs(index, range);
+		  }
+		}
+
+            internal void ToString(StringBuilder sb, int depth)
+            {
+                indent(sb, depth);
+                if (left == null)
+                {
+                    Debug.Assert(right == null);
+                    sb.Append("leaf: " + start + " to " + end);
+                }
+                else
+                {
+                    sb.Append("node: " + start + " to " + end);
+                }
+                if (outputs != null)
+                {
+                    sb.Append(" outputs=");
+                    sb.Append(outputs);
+                }
+                sb.Append('\n');
+
+                if (left != null)
+                {
+                    Debug.Assert(right != null);
+                    left.ToString(sb, depth + 1);
+                    right.ToString(sb, depth + 1);
+                }
+            }
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Range/LongRangeFacetCounts.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Range/LongRangeFacetCounts.cs b/Lucene.Net.Facet/Range/LongRangeFacetCounts.cs
new file mode 100644
index 0000000..d8dfb15
--- /dev/null
+++ b/Lucene.Net.Facet/Range/LongRangeFacetCounts.cs
@@ -0,0 +1,143 @@
+using System.Collections.Generic;
+using Lucene.Net.Facet;
+
+namespace Lucene.Net.Facet.Range
+{
+
+    /*
+     * 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 MatchingDocs = FacetsCollector.MatchingDocs;
+    using FunctionValues = Lucene.Net.Queries.Function.FunctionValues;
+    using ValueSource = Lucene.Net.Queries.Function.ValueSource;
+    using LongFieldSource = Lucene.Net.Queries.Function.ValueSources.LongFieldSource;
+    using DocIdSet = Lucene.Net.Search.DocIdSet;
+    using Filter = Lucene.Net.Search.Filter;
+    using Bits = Lucene.Net.Util.Bits;
+    using DocIdSetIterator = Lucene.Net.Search.DocIdSetIterator;
+
+    /// <summary>
+    /// <seealso cref="Facets"/> implementation that computes counts for
+    ///  dynamic long ranges from a provided <seealso cref="ValueSource"/>,
+    ///  using <seealso cref="FunctionValues#longVal"/>.  Use
+    ///  this for dimensions that change in real-time (e.g. a
+    ///  relative time based dimension like "Past day", "Past 2
+    ///  days", etc.) or that change for each request (e.g. 
+    ///  distance from the user's location, "< 1 km", "< 2 km",
+    ///  etc.).
+    /// 
+    ///  @lucene.experimental 
+    /// </summary>
+    public class LongRangeFacetCounts : RangeFacetCounts
+    {
+
+        /// <summary>
+        /// Create {@code LongRangeFacetCounts}, using {@link
+        ///  LongFieldSource} from the specified field. 
+        /// </summary>
+        public LongRangeFacetCounts(string field, FacetsCollector hits, params LongRange[] ranges)
+            : this(field, new LongFieldSource(field), hits, ranges)
+        {
+        }
+
+        /// <summary>
+        /// Create {@code RangeFacetCounts}, using the provided
+        ///  <seealso cref="ValueSource"/>. 
+        /// </summary>
+        public LongRangeFacetCounts(string field, ValueSource valueSource, FacetsCollector hits, params LongRange[] ranges)
+            : this(field, valueSource, hits, null, ranges)
+        {
+        }
+
+        /// <summary>
+        /// Create {@code RangeFacetCounts}, using the provided
+        ///  <seealso cref="ValueSource"/>, and using the provided Filter as
+        ///  a fastmatch: only documents passing the filter are
+        ///  checked for the matching ranges.  The filter must be
+        ///  random access (implement <seealso cref="DocIdSet#bits"/>). 
+        /// </summary>
+        public LongRangeFacetCounts(string field, ValueSource valueSource, FacetsCollector hits, Filter fastMatchFilter, params LongRange[] ranges)
+            : base(field, ranges, fastMatchFilter)
+        {
+            Count(valueSource, hits.GetMatchingDocs);
+        }
+
+        private void Count(ValueSource valueSource, IList<MatchingDocs> matchingDocs)
+        {
+
+            LongRange[] ranges = (LongRange[])this.ranges;
+
+            LongRangeCounter counter = new LongRangeCounter(ranges);
+
+            int missingCount = 0;
+            foreach (MatchingDocs hits in matchingDocs)
+            {
+                FunctionValues fv = valueSource.GetValues(new Dictionary<string, object>(), hits.context);
+
+                totCount += hits.totalHits;
+                Bits bits;
+                if (fastMatchFilter != null)
+                {
+                    DocIdSet dis = fastMatchFilter.GetDocIdSet(hits.context, null);
+                    if (dis == null)
+                    {
+                        // No documents match
+                        continue;
+                    }
+                    bits = dis.GetBits();
+                    if (bits == null)
+                    {
+                        throw new System.ArgumentException("fastMatchFilter does not implement DocIdSet.bits");
+                    }
+                }
+                else
+                {
+                    bits = null;
+                }
+
+                DocIdSetIterator docs = hits.bits.GetIterator();
+                int doc;
+                while ((doc = docs.NextDoc()) != DocIdSetIterator.NO_MORE_DOCS)
+                {
+                    if (bits != null && bits.Get(doc) == false)
+                    {
+                        doc++;
+                        continue;
+                    }
+                    // Skip missing docs:
+                    if (fv.Exists(doc))
+                    {
+                        counter.add(fv.LongVal(doc));
+                    }
+                    else
+                    {
+                        missingCount++;
+                    }
+                }
+            }
+
+            int x = counter.fillCounts(counts);
+
+            missingCount += x;
+
+            //System.out.println("totCount " + totCount + " missingCount " + counter.missingCount);
+            totCount -= missingCount;
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Range/Range.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Range/Range.cs b/Lucene.Net.Facet/Range/Range.cs
new file mode 100644
index 0000000..548b915
--- /dev/null
+++ b/Lucene.Net.Facet/Range/Range.cs
@@ -0,0 +1,90 @@
+using Lucene.Net.Facet;
+
+namespace Lucene.Net.Facet.Range
+{
+
+    /*
+     * 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 ValueSource = Lucene.Net.Queries.Function.ValueSource;
+    using Filter = Lucene.Net.Search.Filter;
+    using FilteredQuery = Lucene.Net.Search.FilteredQuery; // javadocs
+    using Lucene.Net.Search; // javadocs
+
+    /// <summary>
+    /// Base class for a single labeled range.
+    /// 
+    ///  @lucene.experimental 
+    /// </summary>
+    public abstract class Range
+    {
+
+        /// <summary>
+        /// Label that identifies this range. </summary>
+        public readonly string Label;
+
+        /// <summary>
+        /// Sole constructor. </summary>
+        protected internal Range(string label)
+        {
+            if (label == null)
+            {
+                throw new System.NullReferenceException("label cannot be null");
+            }
+            this.Label = label;
+        }
+
+        /// <summary>
+        /// Returns a new <seealso cref="Filter"/> accepting only documents
+        ///  in this range.  This filter is not general-purpose;
+        ///  you should either use it with <seealso cref="DrillSideways"/> by
+        ///  adding it to <seealso cref="DrillDownQuery#add"/>, or pass it to
+        ///  <seealso cref="FilteredQuery"/> using its {@link
+        ///  FilteredQuery#QUERY_FIRST_FILTER_STRATEGY}.  If the
+        ///  <seealso cref="ValueSource"/> is static, e.g. an indexed numeric
+        ///  field, then it may be more efficient to use {@link
+        ///  NumericRangeFilter}.  The provided fastMatchFilter,
+        ///  if non-null, will first be consulted, and only if
+        ///  that is set for each document will the range then be
+        ///  checked. 
+        /// </summary>
+        public abstract Filter GetFilter(Filter fastMatchFilter, ValueSource valueSource);
+
+        /// <summary>
+        /// Returns a new <seealso cref="Filter"/> accepting only documents
+        ///  in this range.  This filter is not general-purpose;
+        ///  you should either use it with <seealso cref="DrillSideways"/> by
+        ///  adding it to <seealso cref="DrillDownQuery#add"/>, or pass it to
+        ///  <seealso cref="FilteredQuery"/> using its {@link
+        ///  FilteredQuery#QUERY_FIRST_FILTER_STRATEGY}.  If the
+        ///  <seealso cref="ValueSource"/> is static, e.g. an indexed numeric
+        ///  field, then it may be more efficient to use <seealso cref="NumericRangeFilter"/>. 
+        /// </summary>
+        public virtual Filter GetFilter(ValueSource valueSource)
+        {
+            return GetFilter(null, valueSource);
+        }
+
+        /// <summary>
+        /// Invoke this for a useless range. </summary>
+        protected internal virtual void FailNoMatch()
+        {
+            throw new System.ArgumentException("range \"" + Label + "\" matches nothing");
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Range/RangeFacetCounts.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Range/RangeFacetCounts.cs b/Lucene.Net.Facet/Range/RangeFacetCounts.cs
new file mode 100644
index 0000000..11c7e09
--- /dev/null
+++ b/Lucene.Net.Facet/Range/RangeFacetCounts.cs
@@ -0,0 +1,99 @@
+using System.Collections.Generic;
+using Lucene.Net.Facet;
+using Lucene.Net.Support;
+
+namespace Lucene.Net.Facet.Range
+{
+
+    /*
+     * 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 Filter = Lucene.Net.Search.Filter;
+
+    /// <summary>
+    /// Base class for range faceting.
+    /// 
+    ///  @lucene.experimental 
+    /// </summary>
+    public abstract class RangeFacetCounts : Facets
+    {
+        /// <summary>
+        /// Ranges passed to constructor. </summary>
+        protected internal readonly Range[] ranges;
+
+        /// <summary>
+        /// Counts, initialized in by subclass. </summary>
+        protected internal readonly int[] counts;
+
+        /// <summary>
+        /// Optional: if specified, we first test this Filter to
+        ///  see whether the document should be checked for
+        ///  matching ranges.  If this is null, all documents are
+        ///  checked. 
+        /// </summary>
+        protected internal readonly Filter fastMatchFilter;
+
+        /// <summary>
+        /// Our field name. </summary>
+        protected internal readonly string field;
+
+        /// <summary>
+        /// Total number of hits. </summary>
+        protected internal int totCount;
+
+        /// <summary>
+        /// Create {@code RangeFacetCounts} </summary>
+        protected internal RangeFacetCounts(string field, Range[] ranges, Filter fastMatchFilter)
+        {
+            this.field = field;
+            this.ranges = ranges;
+            this.fastMatchFilter = fastMatchFilter;
+            counts = new int[ranges.Length];
+        }
+
+        public override FacetResult GetTopChildren(int topN, string dim, params string[] path)
+        {
+            if (dim.Equals(field) == false)
+            {
+                throw new System.ArgumentException("invalid dim \"" + dim + "\"; should be \"" + field + "\"");
+            }
+            if (path.Length != 0)
+            {
+                throw new System.ArgumentException("path.length should be 0");
+            }
+            LabelAndValue[] labelValues = new LabelAndValue[counts.Length];
+            for (int i = 0; i < counts.Length; i++)
+            {
+                labelValues[i] = new LabelAndValue(ranges[i].Label, counts[i]);
+            }
+            return new FacetResult(dim, path, totCount, labelValues, labelValues.Length);
+        }
+
+        public override float GetSpecificValue(string dim, params string[] path)
+        {
+            // TODO: should we impl this?
+            throw new System.NotSupportedException();
+        }
+
+        public override IList<FacetResult> GetAllDims(int topN)
+        {
+            return new[] { GetTopChildren(topN, null) };
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/SortedSet/DefaultSortedSetDocValuesReaderState.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/SortedSet/DefaultSortedSetDocValuesReaderState.cs b/Lucene.Net.Facet/SortedSet/DefaultSortedSetDocValuesReaderState.cs
new file mode 100644
index 0000000..8113d80
--- /dev/null
+++ b/Lucene.Net.Facet/SortedSet/DefaultSortedSetDocValuesReaderState.cs
@@ -0,0 +1,158 @@
+using System.Collections.Generic;
+using Lucene.Net.Index;
+using Lucene.Net.Support;
+using Lucene.Net.Util;
+using Lucene.Net.Facet;
+
+namespace Lucene.Net.Facet.SortedSet
+{
+    /*
+	 * 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.
+	 */
+
+    /// <summary>
+    /// Default implementation of <seealso cref="SortedSetDocValuesFacetCounts"/>
+    /// </summary>
+    public class DefaultSortedSetDocValuesReaderState : SortedSetDocValuesReaderState
+    {
+
+        private readonly string field;
+        private readonly AtomicReader topReader;
+        private readonly int valueCount;
+
+        /// <summary>
+        /// <seealso cref="IndexReader"/> passed to the constructor. </summary>
+        public readonly IndexReader origReader;
+
+        private readonly IDictionary<string, OrdRange> prefixToOrdRange = new Dictionary<string, OrdRange>();
+
+        /// <summary>
+        /// Creates this, pulling doc values from the specified
+        ///  field. 
+        /// </summary>
+        public DefaultSortedSetDocValuesReaderState(IndexReader reader, string field = FacetsConfig.DEFAULT_INDEX_FIELD_NAME)
+        {
+            this.field = field;
+            this.origReader = reader;
+
+            // We need this to create thread-safe MultiSortedSetDV
+            // per collector:
+            topReader = SlowCompositeReaderWrapper.Wrap(reader);
+            SortedSetDocValues dv = topReader.GetSortedSetDocValues(field);
+            if (dv == null)
+            {
+                throw new System.ArgumentException("field \"" + field + "\" was not indexed with SortedSetDocValues");
+            }
+            if (dv.ValueCount > int.MaxValue)
+            {
+                throw new System.ArgumentException("can only handle valueCount < Integer.MAX_VALUE; got " + dv.ValueCount);
+            }
+            valueCount = (int)dv.ValueCount;
+
+            // TODO: we can make this more efficient if eg we can be
+            // "involved" when OrdinalMap is being created?  Ie see
+            // each term/ord it's assigning as it goes...
+            string lastDim = null;
+            int startOrd = -1;
+
+            // TODO: this approach can work for full hierarchy?;
+            // TaxoReader can't do this since ords are not in
+            // "sorted order" ... but we should generalize this to
+            // support arbitrary hierarchy:
+            for (int ord = 0; ord < valueCount; ord++)
+            {
+                BytesRef term = new BytesRef();
+                dv.LookupOrd(ord, term);
+                string[] components = FacetsConfig.StringToPath(term.Utf8ToString());
+                if (components.Length != 2)
+                {
+                    throw new System.ArgumentException("this class can only handle 2 level hierarchy (dim/value); got: " + Arrays.ToString(components) + " " + term.Utf8ToString());
+                }
+                if (!components[0].Equals(lastDim))
+                {
+                    if (lastDim != null)
+                    {
+                        prefixToOrdRange[lastDim] = new OrdRange(startOrd, ord - 1);
+                    }
+                    startOrd = ord;
+                    lastDim = components[0];
+                }
+            }
+
+            if (lastDim != null)
+            {
+                prefixToOrdRange[lastDim] = new OrdRange(startOrd, valueCount - 1);
+            }
+        }
+
+        /// <summary>
+        /// Return top-level doc values. </summary>
+        public override SortedSetDocValues DocValues
+        {
+            get
+            {
+                return topReader.GetSortedSetDocValues(field);
+            }
+        }
+
+        /// <summary>
+        /// Returns mapping from prefix to <seealso cref="SortedSetDocValuesReaderState.OrdRange"/>. </summary>
+        public override IDictionary<string, OrdRange> PrefixToOrdRange
+        {
+            get
+            {
+                return prefixToOrdRange;
+            }
+        }
+
+        /// <summary>
+        /// Returns the <seealso cref="SortedSetDocValuesReaderState.OrdRange"/> for this dimension. </summary>
+        public override OrdRange GetOrdRange(string dim)
+        {
+            return prefixToOrdRange[dim];
+        }
+
+        /// <summary>
+        /// Indexed field we are reading. </summary>
+        public override string Field
+        {
+            get
+            {
+                return field;
+            }
+        }
+
+        public override IndexReader OrigReader
+        {
+            get
+            {
+                return origReader;
+            }
+        }
+
+        /// <summary>
+        /// Number of unique labels. </summary>
+        public override int Size
+        {
+            get
+            {
+                return valueCount;
+            }
+        }
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/SortedSet/SortedSetDocValuesFacetCounts.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/SortedSet/SortedSetDocValuesFacetCounts.cs b/Lucene.Net.Facet/SortedSet/SortedSetDocValuesFacetCounts.cs
new file mode 100644
index 0000000..aa2f40a
--- /dev/null
+++ b/Lucene.Net.Facet/SortedSet/SortedSetDocValuesFacetCounts.cs
@@ -0,0 +1,350 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using Lucene.Net.Facet;
+
+namespace Lucene.Net.Facet.SortedSet
+{
+
+    /*
+     * 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 MatchingDocs = FacetsCollector.MatchingDocs;
+    using OrdRange = Lucene.Net.Facet.SortedSet.SortedSetDocValuesReaderState.OrdRange;
+    using AtomicReader = Lucene.Net.Index.AtomicReader;
+    using IndexReader = Lucene.Net.Index.IndexReader;
+    using MultiDocValues = Lucene.Net.Index.MultiDocValues;
+    using MultiSortedSetDocValues = Lucene.Net.Index.MultiDocValues.MultiSortedSetDocValues;
+    using ReaderUtil = Lucene.Net.Index.ReaderUtil;
+    using SortedSetDocValues = Lucene.Net.Index.SortedSetDocValues;
+    using DocIdSetIterator = Lucene.Net.Search.DocIdSetIterator;
+    using BytesRef = Lucene.Net.Util.BytesRef;
+    using LongValues = Lucene.Net.Util.LongValues;
+
+    /// <summary>
+    /// Compute facets counts from previously
+    ///  indexed <seealso cref="SortedSetDocValuesFacetField"/>,
+    ///  without require a separate taxonomy index.  Faceting is
+    ///  a bit slower (~25%), and there is added cost on every
+    ///  <seealso cref="IndexReader"/> open to create a new {@link
+    ///  SortedSetDocValuesReaderState}.  Furthermore, this does
+    ///  not support hierarchical facets; only flat (dimension +
+    ///  label) facets, but it uses quite a bit less RAM to do
+    ///  so.
+    /// 
+    ///  <para><b>NOTE</b>: this class should be instantiated and
+    ///  then used from a single thread, because it holds a
+    ///  thread-private instance of <seealso cref="SortedSetDocValues"/>.
+    /// 
+    /// </para>
+    /// <para><b>NOTE:</b>: tie-break is by unicode sort order
+    /// 
+    /// @lucene.experimental 
+    /// </para>
+    /// </summary>
+    public class SortedSetDocValuesFacetCounts : Facets
+    {
+
+        internal readonly SortedSetDocValuesReaderState state;
+        internal readonly SortedSetDocValues dv;
+        internal readonly string field;
+        internal readonly int[] counts;
+
+        /// <summary>
+        /// Sparse faceting: returns any dimension that had any
+        ///  hits, topCount labels per dimension. 
+        /// </summary>
+        public SortedSetDocValuesFacetCounts(SortedSetDocValuesReaderState state, FacetsCollector hits)
+        {
+            this.state = state;
+            this.field = state.Field;
+            dv = state.DocValues;
+            counts = new int[state.Size];
+            //System.out.println("field=" + field);
+            Count(hits.GetMatchingDocs);
+        }
+
+        public override FacetResult GetTopChildren(int topN, string dim, params string[] path)
+        {
+            if (topN <= 0)
+            {
+                throw new System.ArgumentException("topN must be > 0 (got: " + topN + ")");
+            }
+            if (path.Length > 0)
+            {
+                throw new System.ArgumentException("path should be 0 length");
+            }
+            OrdRange ordRange = state.GetOrdRange(dim);
+            if (ordRange == null)
+            {
+                throw new System.ArgumentException("dimension \"" + dim + "\" was not indexed");
+            }
+            return GetDim(dim, ordRange, topN);
+        }
+
+        private FacetResult GetDim(string dim, OrdRange ordRange, int topN)
+        {
+
+            TopOrdAndIntQueue q = null;
+
+            int bottomCount = 0;
+
+            int dimCount = 0;
+            int childCount = 0;
+
+            TopOrdAndIntQueue.OrdAndValue reuse = null;
+            //System.out.println("getDim : " + ordRange.start + " - " + ordRange.end);
+            for (int ord = ordRange.Start; ord <= ordRange.End; ord++)
+            {
+                //System.out.println("  ord=" + ord + " count=" + counts[ord]);
+                if (counts[ord] > 0)
+                {
+                    dimCount += counts[ord];
+                    childCount++;
+                    if (counts[ord] > bottomCount)
+                    {
+                        if (reuse == null)
+                        {
+                            reuse = new TopOrdAndIntQueue.OrdAndValue();
+                        }
+                        reuse.ord = ord;
+                        reuse.value = counts[ord];
+                        if (q == null)
+                        {
+                            // Lazy init, so we don't create this for the
+                            // sparse case unnecessarily
+                            q = new TopOrdAndIntQueue(topN);
+                        }
+                        reuse = q.InsertWithOverflow(reuse);
+                        if (q.Size() == topN)
+                        {
+                            bottomCount = q.Top().value;
+                        }
+                    }
+                }
+            }
+
+            if (q == null)
+            {
+                return null;
+            }
+
+            LabelAndValue[] labelValues = new LabelAndValue[q.Size()];
+            for (int i = labelValues.Length - 1; i >= 0; i--)
+            {
+                TopOrdAndIntQueue.OrdAndValue ordAndValue = q.Pop();
+                var term = new BytesRef();
+                dv.LookupOrd(ordAndValue.ord, term);
+                string[] parts = FacetsConfig.StringToPath(term.Utf8ToString());
+                labelValues[i] = new LabelAndValue(parts[1], ordAndValue.value);
+            }
+
+            return new FacetResult(dim, new string[0], dimCount, labelValues, childCount);
+        }
+
+        /// <summary>
+        /// Does all the "real work" of tallying up the counts. </summary>
+        private void Count(IList<FacetsCollector.MatchingDocs> matchingDocs)
+        {
+            //System.out.println("ssdv count");
+
+            MultiDocValues.OrdinalMap ordinalMap;
+
+            // TODO: is this right?  really, we need a way to
+            // verify that this ordinalMap "matches" the leaves in
+            // matchingDocs...
+            if (dv is MultiDocValues.MultiSortedSetDocValues && matchingDocs.Count > 1)
+            {
+                ordinalMap = ((MultiDocValues.MultiSortedSetDocValues)dv).Mapping;
+            }
+            else
+            {
+                ordinalMap = null;
+            }
+
+            IndexReader origReader = state.OrigReader;
+
+            foreach (FacetsCollector.MatchingDocs hits in matchingDocs)
+            {
+
+                AtomicReader reader = hits.context.AtomicReader;
+                //System.out.println("  reader=" + reader);
+                // LUCENE-5090: make sure the provided reader context "matches"
+                // the top-level reader passed to the
+                // SortedSetDocValuesReaderState, else cryptic
+                // AIOOBE can happen:
+                if (ReaderUtil.GetTopLevelContext(hits.context).Reader != origReader)
+                {
+                    throw new ThreadStateException("the SortedSetDocValuesReaderState provided to this class does not match the reader being searched; you must create a new SortedSetDocValuesReaderState every time you open a new IndexReader");
+                }
+
+                SortedSetDocValues segValues = reader.GetSortedSetDocValues(field);
+                if (segValues == null)
+                {
+                    continue;
+                }
+
+                DocIdSetIterator docs = hits.bits.GetIterator();
+
+                // TODO: yet another option is to count all segs
+                // first, only in seg-ord space, and then do a
+                // merge-sort-PQ in the end to only "resolve to
+                // global" those seg ords that can compete, if we know
+                // we just want top K?  ie, this is the same algo
+                // that'd be used for merging facets across shards
+                // (distributed faceting).  but this has much higher
+                // temp ram req'ts (sum of number of ords across all
+                // segs)
+                if (ordinalMap != null)
+                {
+                    int segOrd = hits.context.Ord;
+
+                    int numSegOrds = (int)segValues.ValueCount;
+
+                    if (hits.totalHits < numSegOrds / 10)
+                    {
+                        //System.out.println("    remap as-we-go");
+                        // Remap every ord to global ord as we iterate:
+                        int doc;
+                        while ((doc = docs.NextDoc()) != DocIdSetIterator.NO_MORE_DOCS)
+                        {
+                            //System.out.println("    doc=" + doc);
+                            segValues.Document = doc;
+                            int term = (int)segValues.NextOrd();
+                            while (term != SortedSetDocValues.NO_MORE_ORDS)
+                            {
+                                //System.out.println("      segOrd=" + segOrd + " ord=" + term + " globalOrd=" + ordinalMap.getGlobalOrd(segOrd, term));
+                                counts[(int)ordinalMap.GetGlobalOrd(segOrd, term)]++;
+                                term = (int)segValues.NextOrd();
+                            }
+                        }
+                    }
+                    else
+                    {
+                        //System.out.println("    count in seg ord first");
+
+                        // First count in seg-ord space:
+                        int[] segCounts = new int[numSegOrds];
+                        int doc;
+                        while ((doc = docs.NextDoc()) != DocIdSetIterator.NO_MORE_DOCS)
+                        {
+                            //System.out.println("    doc=" + doc);
+                            segValues.Document = doc;
+                            int term = (int)segValues.NextOrd();
+                            while (term != SortedSetDocValues.NO_MORE_ORDS)
+                            {
+                                //System.out.println("      ord=" + term);
+                                segCounts[term]++;
+                                term = (int)segValues.NextOrd();
+                            }
+                        }
+
+                        // Then, migrate to global ords:
+                        for (int ord = 0; ord < numSegOrds; ord++)
+                        {
+                            int count = segCounts[ord];
+                            if (count != 0)
+                            {
+                                //System.out.println("    migrate segOrd=" + segOrd + " ord=" + ord + " globalOrd=" + ordinalMap.getGlobalOrd(segOrd, ord));
+                                counts[(int)ordinalMap.GetGlobalOrd(segOrd, ord)] += count;
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    // No ord mapping (e.g., single segment index):
+                    // just aggregate directly into counts:
+                    int doc;
+                    while ((doc = docs.NextDoc()) != DocIdSetIterator.NO_MORE_DOCS)
+                    {
+                        segValues.Document = doc;
+                        int term = (int)segValues.NextOrd();
+                        while (term != SortedSetDocValues.NO_MORE_ORDS)
+                        {
+                            counts[term]++;
+                            term = (int)segValues.NextOrd();
+                        }
+                    }
+                }
+            }
+        }
+
+        public override float GetSpecificValue(string dim, params string[] path)
+        {
+            if (path.Length != 1)
+            {
+                throw new System.ArgumentException("path must be length=1");
+            }
+            int ord = (int)dv.LookupTerm(new BytesRef(FacetsConfig.PathToString(dim, path)));
+            if (ord < 0)
+            {
+                return -1;
+            }
+
+            return counts[ord];
+        }
+
+        public override IList<FacetResult> GetAllDims(int topN)
+        {
+
+            IList<FacetResult> results = new List<FacetResult>();
+            foreach (KeyValuePair<string, OrdRange> ent in state.PrefixToOrdRange)
+            {
+                FacetResult fr = GetDim(ent.Key, ent.Value, topN);
+                if (fr != null)
+                {
+                    results.Add(fr);
+                }
+            }
+
+            var resultArray = results.ToArray();
+            // Sort by highest count:
+            Array.Sort(resultArray, new ComparatorAnonymousInnerClassHelper(this));
+            return resultArray;
+        }
+
+        private class ComparatorAnonymousInnerClassHelper : IComparer<FacetResult>
+        {
+            private readonly SortedSetDocValuesFacetCounts outerInstance;
+
+            public ComparatorAnonymousInnerClassHelper(SortedSetDocValuesFacetCounts outerInstance)
+            {
+                this.outerInstance = outerInstance;
+            }
+
+            public virtual int Compare(FacetResult a, FacetResult b)
+            {
+                if ((int)a.value > (int)b.value)
+                {
+                    return -1;
+                }
+                else if ((int)b.value > (int)a.value)
+                {
+                    return 1;
+                }
+                else
+                {
+                    return a.dim.CompareTo(b.dim);
+                }
+            }
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/SortedSet/SortedSetDocValuesFacetField.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/SortedSet/SortedSetDocValuesFacetField.cs b/Lucene.Net.Facet/SortedSet/SortedSetDocValuesFacetField.cs
new file mode 100644
index 0000000..ee011a3
--- /dev/null
+++ b/Lucene.Net.Facet/SortedSet/SortedSetDocValuesFacetField.cs
@@ -0,0 +1,65 @@
+namespace Lucene.Net.Facet.SortedSet
+{
+
+    /*
+     * 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 Field = Lucene.Net.Documents.Field;
+    using FieldType = Lucene.Net.Documents.FieldType;
+
+    /// <summary>
+    /// Add an instance of this to your Document for every facet
+    ///  label to be indexed via SortedSetDocValues. 
+    /// </summary>
+    public class SortedSetDocValuesFacetField : Field
+    {
+
+        /// <summary>
+        /// Indexed <seealso cref="FieldType"/>. </summary>
+        public static readonly FieldType TYPE = new FieldType();
+        static SortedSetDocValuesFacetField()
+        {
+            TYPE.Indexed = true;
+            TYPE.Freeze();
+        }
+
+        /// <summary>
+        /// Dimension. </summary>
+        public readonly string Dim;
+
+        /// <summary>
+        /// Label. </summary>
+        public readonly string Label;
+
+        /// <summary>
+        /// Sole constructor. </summary>
+        public SortedSetDocValuesFacetField(string dim, string label)
+            : base("dummy", TYPE)
+        {
+            FacetField.verifyLabel(label);
+            FacetField.verifyLabel(dim);
+            this.Dim = dim;
+            this.Label = label;
+        }
+
+        public override string ToString()
+        {
+            return "SortedSetDocValuesFacetField(dim=" + Dim + " label=" + Label + ")";
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/SortedSet/SortedSetDocValuesReaderState.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/SortedSet/SortedSetDocValuesReaderState.cs b/Lucene.Net.Facet/SortedSet/SortedSetDocValuesReaderState.cs
new file mode 100644
index 0000000..ef7e0fe
--- /dev/null
+++ b/Lucene.Net.Facet/SortedSet/SortedSetDocValuesReaderState.cs
@@ -0,0 +1,103 @@
+using System.Collections.Generic;
+
+namespace Lucene.Net.Facet.SortedSet
+{
+
+    /*
+     * 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 IndexReader = Lucene.Net.Index.IndexReader;
+    using SortedSetDocValues = Lucene.Net.Index.SortedSetDocValues;
+
+    /// <summary>
+    /// Wraps a <seealso cref="IndexReader"/> and resolves ords
+    ///  using existing <seealso cref="SortedSetDocValues"/> APIs without a
+    ///  separate taxonomy index.  This only supports flat facets
+    ///  (dimension + label), and it makes faceting a bit
+    ///  slower, adds some cost at reopen time, but avoids
+    ///  managing the separate taxonomy index.  It also requires
+    ///  less RAM than the taxonomy index, as it manages the flat
+    ///  (2-level) hierarchy more efficiently.  In addition, the
+    ///  tie-break during faceting is now meaningful (in label
+    ///  sorted order).
+    /// 
+    ///  <para><b>NOTE</b>: creating an instance of this class is
+    ///  somewhat costly, as it computes per-segment ordinal maps,
+    ///  so you should create it once and re-use that one instance
+    ///  for a given <seealso cref="IndexReader"/>. 
+    /// </para>
+    /// </summary>
+
+    public abstract class SortedSetDocValuesReaderState
+    {
+
+        /// <summary>
+        /// Holds start/end range of ords, which maps to one
+        ///  dimension (someday we may generalize it to map to
+        ///  hierarchies within one dimension). 
+        /// </summary>
+        public sealed class OrdRange
+        {
+            /// <summary>
+            /// Start of range, inclusive: </summary>
+            public readonly int Start;
+            /// <summary>
+            /// End of range, inclusive: </summary>
+            public readonly int End;
+
+            /// <summary>
+            /// Start and end are inclusive. </summary>
+            public OrdRange(int start, int end)
+            {
+                this.Start = start;
+                this.End = end;
+            }
+        }
+
+        /// <summary>
+        /// Sole constructor. </summary>
+        protected internal SortedSetDocValuesReaderState()
+        {
+        }
+
+        /// <summary>
+        /// Return top-level doc values. </summary>
+        public abstract SortedSetDocValues DocValues { get; }
+
+        /// <summary>
+        /// Indexed field we are reading. </summary>
+        public abstract string Field { get; }
+
+        /// <summary>
+        /// Returns the <seealso cref="OrdRange"/> for this dimension. </summary>
+        public abstract OrdRange GetOrdRange(string dim);
+
+        /// <summary>
+        /// Returns mapping from prefix to <seealso cref="OrdRange"/>. </summary>
+        public abstract IDictionary<string, OrdRange> PrefixToOrdRange { get; }
+
+        /// <summary>
+        /// Returns top-level index reader. </summary>
+        public abstract IndexReader OrigReader { get; }
+
+        /// <summary>
+        /// Number of unique labels. </summary>
+        public abstract int Size { get; }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/AssociationFacetField.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/AssociationFacetField.cs b/Lucene.Net.Facet/Taxonomy/AssociationFacetField.cs
new file mode 100644
index 0000000..5835fd2
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/AssociationFacetField.cs
@@ -0,0 +1,91 @@
+using Lucene.Net.Facet;
+using Lucene.Net.Support;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * 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 Document = Lucene.Net.Documents.Document; // javadocs
+    using Field = Lucene.Net.Documents.Field;
+    using FieldType = Lucene.Net.Documents.FieldType;
+    using BytesRef = Lucene.Net.Util.BytesRef;
+
+    /// <summary>
+    /// Add an instance of this to your <seealso cref="Document"/> to add
+    ///  a facet label associated with an arbitrary byte[].
+    ///  This will require a custom <seealso cref="Facets"/>
+    ///  implementation at search time; see {@link
+    ///  IntAssociationFacetField} and {@link
+    ///  FloatAssociationFacetField} to use existing {@link
+    ///  Facets} implementations.
+    /// 
+    ///  @lucene.experimental 
+    /// </summary>
+    public class AssociationFacetField : Field
+    {
+
+        /// <summary>
+        /// Indexed <seealso cref="FieldType"/>. </summary>
+        public static readonly FieldType TYPE = new FieldType();
+        static AssociationFacetField()
+        {
+            TYPE.Indexed = true;
+            TYPE.Freeze();
+        }
+
+        /// <summary>
+        /// Dimension for this field. </summary>
+        public readonly string dim;
+
+        /// <summary>
+        /// Facet path for this field. </summary>
+        public readonly string[] path;
+
+        /// <summary>
+        /// Associated value. </summary>
+        public readonly BytesRef assoc;
+
+        /// <summary>
+        /// Creates this from {@code dim} and {@code path} and an
+        ///  association 
+        /// </summary>
+        public AssociationFacetField(BytesRef assoc, string dim, params string[] path)
+            : base("dummy", TYPE)
+        {
+            FacetField.verifyLabel(dim);
+            foreach (string label in path)
+            {
+                FacetField.verifyLabel(label);
+            }
+            this.dim = dim;
+            this.assoc = assoc;
+            if (path.Length == 0)
+            {
+                throw new System.ArgumentException("path must have at least one element");
+            }
+            this.path = path;
+        }
+
+        public override string ToString()
+        {
+            return "AssociationFacetField(dim=" + dim + " path=" + Arrays.ToString(path) + " bytes=" + assoc + ")";
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/CachedOrdinalsReader.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/CachedOrdinalsReader.cs b/Lucene.Net.Facet/Taxonomy/CachedOrdinalsReader.cs
new file mode 100644
index 0000000..0471f65
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/CachedOrdinalsReader.cs
@@ -0,0 +1,208 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using Lucene.Net.Support;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * 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 DocValuesFormat = Lucene.Net.Codecs.DocValuesFormat;
+    using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext;
+    using BinaryDocValues = Lucene.Net.Index.BinaryDocValues;
+    using Accountable = Lucene.Net.Util.Accountable;
+    using ArrayUtil = Lucene.Net.Util.ArrayUtil;
+    using IntsRef = Lucene.Net.Util.IntsRef;
+    using RamUsageEstimator = Lucene.Net.Util.RamUsageEstimator;
+
+    /// <summary>
+    /// A per-segment cache of documents' facet ordinals. Every
+    /// <seealso cref="CachedOrds"/> holds the ordinals in a raw {@code
+    /// int[]}, and therefore consumes as much RAM as the total
+    /// number of ordinals found in the segment, but saves the
+    /// CPU cost of decoding ordinals during facet counting.
+    /// 
+    /// <para>
+    /// <b>NOTE:</b> every <seealso cref="CachedOrds"/> is limited to 2.1B
+    /// total ordinals. If that is a limitation for you then
+    /// consider limiting the segment size to fewer documents, or
+    /// use an alternative cache which pages through the category
+    /// ordinals.
+    /// 
+    /// </para>
+    /// <para>
+    /// <b>NOTE:</b> when using this cache, it is advised to use
+    /// a <seealso cref="DocValuesFormat"/> that does not cache the data in
+    /// memory, at least for the category lists fields, or
+    /// otherwise you'll be doing double-caching.
+    /// 
+    /// </para>
+    /// <para>
+    /// <b>NOTE:</b> create one instance of this and re-use it
+    /// for all facet implementations (the cache is per-instance,
+    /// not static).
+    /// </para>
+    /// </summary>
+    public class CachedOrdinalsReader : OrdinalsReader, Accountable
+    {
+
+        private readonly OrdinalsReader source;
+
+        private readonly IDictionary<object, CachedOrds> ordsCache = new WeakDictionary<object, CachedOrds>();
+
+        /// <summary>
+        /// Sole constructor. </summary>
+        public CachedOrdinalsReader(OrdinalsReader source)
+        {
+            this.source = source;
+        }
+
+        private CachedOrds GetCachedOrds(AtomicReaderContext context)
+        {
+            lock (this)
+            {
+                object cacheKey = context.Reader.CoreCacheKey;
+                CachedOrds ords = ordsCache[cacheKey];
+                if (ords == null)
+                {
+                    ords = new CachedOrds(source.GetReader(context), context.Reader.MaxDoc);
+                    ordsCache[cacheKey] = ords;
+                }
+
+                return ords;
+            }
+        }
+
+        public override string IndexFieldName
+        {
+            get
+            {
+                return source.IndexFieldName;
+            }
+        }
+
+        public override OrdinalsSegmentReader GetReader(AtomicReaderContext context)
+        {
+            CachedOrds cachedOrds = GetCachedOrds(context);
+            return new OrdinalsSegmentReaderAnonymousInnerClassHelper(this, cachedOrds);
+        }
+
+        private class OrdinalsSegmentReaderAnonymousInnerClassHelper : OrdinalsSegmentReader
+        {
+            private readonly CachedOrdinalsReader outerInstance;
+
+            private Lucene.Net.Facet.Taxonomy.CachedOrdinalsReader.CachedOrds cachedOrds;
+
+            public OrdinalsSegmentReaderAnonymousInnerClassHelper(CachedOrdinalsReader outerInstance, Lucene.Net.Facet.Taxonomy.CachedOrdinalsReader.CachedOrds cachedOrds)
+            {
+                this.outerInstance = outerInstance;
+                this.cachedOrds = cachedOrds;
+            }
+
+            public override void Get(int docID, IntsRef ordinals)
+            {
+                ordinals.Ints = cachedOrds.ordinals;
+                ordinals.Offset = cachedOrds.offsets[docID];
+                ordinals.Length = cachedOrds.offsets[docID + 1] - ordinals.Offset;
+            }
+        }
+
+        /// <summary>
+        /// Holds the cached ordinals in two parallel {@code int[]} arrays. </summary>
+        public sealed class CachedOrds : Accountable
+        {
+
+            /// <summary>
+            /// Index into <seealso cref="#ordinals"/> for each document. </summary>
+            public readonly int[] offsets;
+
+            /// <summary>
+            /// Holds ords for all docs. </summary>
+            public readonly int[] ordinals;
+
+            /// <summary>
+            /// Creates a new <seealso cref="CachedOrds"/> from the <seealso cref="BinaryDocValues"/>.
+            /// Assumes that the <seealso cref="BinaryDocValues"/> is not {@code null}.
+            /// </summary>
+            public CachedOrds(OrdinalsSegmentReader source, int maxDoc)
+            {
+                offsets = new int[maxDoc + 1];
+                int[] ords = new int[maxDoc]; // let's assume one ordinal per-document as an initial size
+
+                // this aggregator is limited to Integer.MAX_VALUE total ordinals.
+                long totOrds = 0;
+                IntsRef values = new IntsRef(32);
+                for (int docID = 0; docID < maxDoc; docID++)
+                {
+                    offsets[docID] = (int)totOrds;
+                    source.Get(docID, values);
+                    long nextLength = totOrds + values.Length;
+                    if (nextLength > ords.Length)
+                    {
+                        if (nextLength > ArrayUtil.MAX_ARRAY_LENGTH)
+                        {
+                            throw new ThreadStateException("too many ordinals (>= " + nextLength + ") to cache");
+                        }
+                        ords = ArrayUtil.Grow(ords, (int)nextLength);
+                    }
+                    Array.Copy(values.Ints, 0, ords, (int)totOrds, values.Length);
+                    totOrds = nextLength;
+                }
+                offsets[maxDoc] = (int)totOrds;
+
+                // if ords array is bigger by more than 10% of what we really need, shrink it
+                if ((double)totOrds / ords.Length < 0.9)
+                {
+                    this.ordinals = new int[(int)totOrds];
+                    Array.Copy(ords, 0, this.ordinals, 0, (int)totOrds);
+                }
+                else
+                {
+                    this.ordinals = ords;
+                }
+            }
+
+            public long RamBytesUsed()
+            {
+                long mem = RamUsageEstimator.ShallowSizeOf(this) + RamUsageEstimator.SizeOf(offsets);
+                if (offsets != ordinals)
+                {
+                    mem += RamUsageEstimator.SizeOf(ordinals);
+                }
+                return mem;
+            }
+        }
+
+        public virtual long RamBytesUsed()
+        {
+            lock (this)
+            {
+                long bytes = 0;
+                foreach (CachedOrds ords in ordsCache.Values)
+                {
+                    bytes += ords.RamBytesUsed();
+                }
+
+                return bytes;
+            }
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/CategoryPath.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/CategoryPath.cs b/Lucene.Net.Facet/Taxonomy/CategoryPath.cs
new file mode 100644
index 0000000..df69862
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/CategoryPath.cs
@@ -0,0 +1,316 @@
+using System;
+using System.Diagnostics;
+using System.Text;
+using Lucene.Net.Support;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * 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.
+     */
+
+
+    /// <summary>
+    /// Holds a sequence of string components, specifying the hierarchical name of a
+    /// category.
+    /// 
+    /// @lucene.experimental
+    /// </summary>
+    public class CategoryPath : IComparable<CategoryPath>
+    {
+
+        /// <summary>
+        /// An empty <seealso cref="CategoryPath"/> </summary>
+        public static readonly CategoryPath EMPTY = new CategoryPath();
+
+        /// <summary>
+        /// The components of this <seealso cref="CategoryPath"/>. Note that this array may be
+        /// shared with other <seealso cref="CategoryPath"/> instances, e.g. as a result of
+        /// <seealso cref="#subpath(int)"/>, therefore you should traverse the array up to
+        /// <seealso cref="#length"/> for this path's components.
+        /// </summary>
+        public readonly string[] components;
+
+        /// <summary>
+        /// The number of components of this <seealso cref="CategoryPath"/>. </summary>
+        public readonly int length;
+
+        // Used by singleton EMPTY
+        private CategoryPath()
+        {
+            components = null;
+            length = 0;
+        }
+
+        // Used by subpath
+        private CategoryPath(CategoryPath copyFrom, int prefixLen)
+        {
+            // while the code which calls this method is safe, at some point a test
+            // tripped on AIOOBE in toString, but we failed to reproduce. adding the
+            // assert as a safety check.
+            Debug.Assert(prefixLen > 0 && prefixLen <= copyFrom.components.Length, "prefixLen cannot be negative nor larger than the given components' length: prefixLen=" + prefixLen + " components.length=" + copyFrom.components.Length);
+            this.components = copyFrom.components;
+            length = prefixLen;
+        }
+
+        /// <summary>
+        /// Construct from the given path components. </summary>
+        public CategoryPath(params string[] components)
+        {
+            Debug.Assert(components.Length > 0, "use CategoryPath.EMPTY to create an empty path");
+            foreach (string comp in components)
+            {
+                if (string.IsNullOrEmpty(comp))
+                {
+                    throw new System.ArgumentException("empty or null components not allowed: " + Arrays.ToString(components));
+                }
+            }
+            this.components = components;
+            length = components.Length;
+        }
+
+        /// <summary>
+        /// Construct from a given path, separating path components with {@code delimiter}. </summary>
+        public CategoryPath(string pathString, char delimiter)
+        {
+            string[] comps = pathString.Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries);
+            if (comps.Length == 1 && comps[0].Length == 0)
+            {
+                components = null;
+                length = 0;
+            }
+            else
+            {
+                foreach (string comp in comps)
+                {
+                    if (string.IsNullOrEmpty(comp))
+                    {
+                        throw new System.ArgumentException("empty or null components not allowed: " + Arrays.ToString(comps));
+                    }
+                }
+                components = comps;
+                length = components.Length;
+            }
+        }
+
+        /// <summary>
+        /// Returns the number of characters needed to represent the path, including
+        /// delimiter characters, for using with
+        /// <seealso cref="#copyFullPath(char[], int, char)"/>.
+        /// </summary>
+        public virtual int FullPathLength()
+        {
+            if (length == 0)
+            {
+                return 0;
+            }
+
+            int charsNeeded = 0;
+            for (int i = 0; i < length; i++)
+            {
+                charsNeeded += components[i].Length;
+            }
+            charsNeeded += length - 1; // num delimter chars
+            return charsNeeded;
+        }
+
+        /// <summary>
+        /// Compares this path with another <seealso cref="CategoryPath"/> for lexicographic
+        /// order.
+        /// </summary>
+        public virtual int CompareTo(CategoryPath other)
+        {
+            int len = length < other.length ? length : other.length;
+            for (int i = 0, j = 0; i < len; i++, j++)
+            {
+                int cmp = components[i].CompareTo(other.components[j]);
+                if (cmp < 0) // this is 'before'
+                {
+                    return -1;
+                }
+                if (cmp > 0) // this is 'after'
+                {
+                    return 1;
+                }
+            }
+
+            // one is a prefix of the other
+            return length - other.length;
+        }
+
+        private void hasDelimiter(string offender, char delimiter)
+        {
+            throw new System.ArgumentException("delimiter character '" + delimiter + "' (U+" + delimiter.ToString() + ") appears in path component \"" + offender + "\"");
+        }
+
+        private void noDelimiter(char[] buf, int offset, int len, char delimiter)
+        {
+            for (int idx = 0; idx < len; idx++)
+            {
+                if (buf[offset + idx] == delimiter)
+                {
+                    hasDelimiter(new string(buf, offset, len), delimiter);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Copies the path components to the given {@code char[]}, starting at index
+        /// {@code start}. {@code delimiter} is copied between the path components.
+        /// Returns the number of chars copied.
+        /// 
+        /// <para>
+        /// <b>NOTE:</b> this method relies on the array being large enough to hold the
+        /// components and separators - the amount of needed space can be calculated
+        /// with <seealso cref="#fullPathLength()"/>.
+        /// </para>
+        /// </summary>
+        public virtual int CopyFullPath(char[] buf, int start, char delimiter)
+        {
+            if (length == 0)
+            {
+                return 0;
+            }
+
+            int idx = start;
+            int upto = length - 1;
+            for (int i = 0; i < upto; i++)
+            {
+                int len = components[i].Length;
+                components[i].CopyTo(0, buf, idx, len - 0);
+                noDelimiter(buf, idx, len, delimiter);
+                idx += len;
+                buf[idx++] = delimiter;
+            }
+            components[upto].CopyTo(0, buf, idx, components[upto].Length - 0);
+            noDelimiter(buf, idx, components[upto].Length, delimiter);
+
+            return idx + components[upto].Length - start;
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (!(obj is CategoryPath))
+            {
+                return false;
+            }
+
+            CategoryPath other = (CategoryPath)obj;
+            if (length != other.length)
+            {
+                return false; // not same length, cannot be equal
+            }
+
+            // CategoryPaths are more likely to differ at the last components, so start
+            // from last-first
+            for (int i = length - 1; i >= 0; i--)
+            {
+                if (!components[i].Equals(other.components[i]))
+                {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public override int GetHashCode()
+        {
+            if (length == 0)
+            {
+                return 0;
+            }
+
+            int hash = length;
+            for (int i = 0; i < length; i++)
+            {
+                hash = hash * 31 + components[i].GetHashCode();
+            }
+            return hash;
+        }
+
+        /// <summary>
+        /// Calculate a 64-bit hash function for this path. </summary>
+        public virtual long LongHashCode()
+        {
+            if (length == 0)
+            {
+                return 0;
+            }
+
+            long hash = length;
+            for (int i = 0; i < length; i++)
+            {
+                hash = hash * 65599 + components[i].GetHashCode();
+            }
+            return hash;
+        }
+
+        /// <summary>
+        /// Returns a sub-path of this path up to {@code length} components. </summary>
+        public virtual CategoryPath Subpath(int length)
+        {
+            if (length >= this.length || length < 0)
+            {
+                return this;
+            }
+            else if (length == 0)
+            {
+                return EMPTY;
+            }
+            else
+            {
+                return new CategoryPath(this, length);
+            }
+        }
+
+        /// <summary>
+        /// Returns a string representation of the path, separating components with
+        /// '/'.
+        /// </summary>
+        /// <seealso cref= #toString(char) </seealso>
+        public override string ToString()
+        {
+            return ToString('/');
+        }
+
+        /// <summary>
+        /// Returns a string representation of the path, separating components with the
+        /// given delimiter.
+        /// </summary>
+        public virtual string ToString(char delimiter)
+        {
+            if (length == 0)
+            {
+                return "";
+            }
+
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < length; i++)
+            {
+                if (components[i].IndexOf(delimiter) != -1)
+                {
+                    hasDelimiter(components[i], delimiter);
+                }
+                sb.Append(components[i]).Append(delimiter);
+            }
+            sb.Length = sb.Length - 1; // remove last delimiter
+            return sb.ToString();
+        }
+
+    }
+
+}
\ No newline at end of file


[18/26] lucenenet git commit: first commit of facet porting, failing tests will be fixed in next commits.

Posted by sy...@apache.org.
first commit of facet porting, failing tests will be fixed in next commits.


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

Branch: refs/heads/master
Commit: 982eaf60972b5fae8bf4e400cbe0045427f8332e
Parents: e4e81d0
Author: eladmarg <el...@gmail.com>
Authored: Wed Nov 12 18:03:12 2014 -0800
Committer: eladmarg <el...@gmail.com>
Committed: Wed Nov 12 18:03:12 2014 -0800

----------------------------------------------------------------------
 Lucene.Net.Facet/DrillDownQuery.cs              |  388 +++++
 Lucene.Net.Facet/DrillSideways.cs               |  295 ++++
 Lucene.Net.Facet/DrillSidewaysQuery.cs          |  316 +++++
 Lucene.Net.Facet/DrillSidewaysScorer.cs         |  826 +++++++++++
 Lucene.Net.Facet/FacetField.cs                  |   91 ++
 Lucene.Net.Facet/FacetResult.cs                 |  103 ++
 Lucene.Net.Facet/Facets.cs                      |   64 +
 Lucene.Net.Facet/FacetsCollector.cs             |  372 +++++
 Lucene.Net.Facet/FacetsConfig.cs                |  703 +++++++++
 Lucene.Net.Facet/LabelAndValue.cs               |   66 +
 Lucene.Net.Facet/Lucene.Net.Facet.csproj        |  123 ++
 Lucene.Net.Facet/MultiFacets.cs                 |  103 ++
 Lucene.Net.Facet/Properties/AssemblyInfo.cs     |   36 +
 .../RandomSamplingFacetsCollector.cs            |  323 +++++
 Lucene.Net.Facet/Range/DoubleRange.cs           |  242 ++++
 .../Range/DoubleRangeFacetCounts.cs             |  160 +++
 Lucene.Net.Facet/Range/LongRange.cs             |  239 ++++
 Lucene.Net.Facet/Range/LongRangeCounter.cs      |  385 +++++
 Lucene.Net.Facet/Range/LongRangeFacetCounts.cs  |  143 ++
 Lucene.Net.Facet/Range/Range.cs                 |   90 ++
 Lucene.Net.Facet/Range/RangeFacetCounts.cs      |   99 ++
 .../DefaultSortedSetDocValuesReaderState.cs     |  158 +++
 .../SortedSet/SortedSetDocValuesFacetCounts.cs  |  350 +++++
 .../SortedSet/SortedSetDocValuesFacetField.cs   |   65 +
 .../SortedSet/SortedSetDocValuesReaderState.cs  |  103 ++
 .../Taxonomy/AssociationFacetField.cs           |   91 ++
 .../Taxonomy/CachedOrdinalsReader.cs            |  208 +++
 Lucene.Net.Facet/Taxonomy/CategoryPath.cs       |  316 +++++
 Lucene.Net.Facet/Taxonomy/Directory/Consts.cs   |   34 +
 .../Directory/DirectoryTaxonomyReader.cs        |  450 ++++++
 .../Directory/DirectoryTaxonomyWriter.cs        | 1191 ++++++++++++++++
 .../Taxonomy/Directory/TaxonomyIndexArrays.cs   |  252 ++++
 .../Taxonomy/DocValuesOrdinalsReader.cs         |  130 ++
 Lucene.Net.Facet/Taxonomy/FacetLabel.cs         |  224 +++
 .../Taxonomy/FastTaxonomyFacetCounts.cs         |  105 ++
 .../Taxonomy/FloatAssociationFacetField.cs      |   65 +
 .../Taxonomy/FloatTaxonomyFacets.cs             |  183 +++
 .../Taxonomy/IntAssociationFacetField.cs        |   74 +
 Lucene.Net.Facet/Taxonomy/IntTaxonomyFacets.cs  |  189 +++
 Lucene.Net.Facet/Taxonomy/LRUHashMap.cs         |  154 ++
 Lucene.Net.Facet/Taxonomy/OrdinalsReader.cs     |   68 +
 .../Taxonomy/ParallelTaxonomyArrays.cs          |   74 +
 Lucene.Net.Facet/Taxonomy/PrintTaxonomyStats.cs |  121 ++
 .../Taxonomy/SearcherTaxonomyManager.cs         |  179 +++
 .../Taxonomy/TaxonomyFacetCounts.cs             |   77 +
 .../TaxonomyFacetSumFloatAssociations.cs        |   98 ++
 .../Taxonomy/TaxonomyFacetSumIntAssociations.cs |   96 ++
 .../Taxonomy/TaxonomyFacetSumValueSource.cs     |  247 ++++
 Lucene.Net.Facet/Taxonomy/TaxonomyFacets.cs     |  137 ++
 Lucene.Net.Facet/Taxonomy/TaxonomyReader.cs     |  316 +++++
 Lucene.Net.Facet/Taxonomy/TaxonomyWriter.cs     |  125 ++
 .../Taxonomy/WriterCache/CategoryPathUtils.cs   |   99 ++
 .../Taxonomy/WriterCache/CharBlockArray.cs      |  234 +++
 .../WriterCache/Cl2oTaxonomyWriterCache.cs      |  123 ++
 .../Taxonomy/WriterCache/CollisionMap.cs        |  288 ++++
 .../WriterCache/CompactLabelToOrdinal.cs        |  521 +++++++
 .../Taxonomy/WriterCache/LabelToOrdinal.cs      |   83 ++
 .../WriterCache/LruTaxonomyWriterCache.cs       |  149 ++
 .../Taxonomy/WriterCache/NameHashIntCacheLRU.cs |   49 +
 .../Taxonomy/WriterCache/NameIntCacheLRU.cs     |  167 +++
 .../Taxonomy/WriterCache/TaxonomyWriterCache.cs |  105 ++
 Lucene.Net.Facet/TopOrdAndFloatQueue.cs         |   73 +
 Lucene.Net.Facet/TopOrdAndIntQueue.cs           |   76 +
 Lucene.Net.sln                                  |   14 +-
 .../Index/BinaryDocValuesFieldUpdates.cs        |    4 +-
 .../Index/NumericDocValuesFieldUpdates.cs       |    4 +-
 src/Lucene.Net.Core/Index/ReaderManager.cs      |    8 +-
 src/Lucene.Net.Core/Lucene.Net.csproj           |    2 +
 .../Search/CachingWrapperFilter.cs              |    2 +-
 src/Lucene.Net.Core/Search/Query.cs             |    2 +-
 src/Lucene.Net.Core/Search/ReferenceManager.cs  |    8 +-
 src/Lucene.Net.Core/Search/SearcherManager.cs   |    8 +-
 .../Search/Spans/NearSpansOrdered.cs            |    4 +-
 src/Lucene.Net.Core/Support/StreamUtils.cs      |   40 +
 src/Lucene.Net.Core/Util/Accountable.cs         |   36 +
 .../Util/ArrayInPlaceMergeSorter.cs             |    4 +-
 src/Lucene.Net.Core/Util/ArrayIntroSorter.cs    |    4 +-
 src/Lucene.Net.Core/Util/ArrayTimSorter.cs      |    4 +-
 src/Lucene.Net.Core/Util/BytesRefArray.cs       |    4 +-
 src/Lucene.Net.Core/Util/BytesRefHash.cs        |    4 +-
 src/Lucene.Net.Core/Util/CollectionUtil.cs      |    8 +-
 src/Lucene.Net.Core/Util/IOUtils.cs             |    2 +-
 src/Lucene.Net.Core/Util/Sorter.cs              |    4 +-
 src/Lucene.Net.Tests/Lucene.Net.Tests.csproj    |   33 +
 .../Facet/AssertingSubDocsAtOnceCollector.cs    |   85 ++
 .../core/Facet/FacetTestCase.cs                 |  293 ++++
 .../core/Facet/Range/TestRangeFacetCounts.cs    | 1174 +++++++++++++++
 .../core/Facet/SlowRAMDirectory.cs              |  261 ++++
 .../SortedSet/TestSortedSetDocValuesFacets.cs   |  394 ++++++
 .../Facet/Taxonomy/Directory/TestAddTaxonomy.cs |  323 +++++
 .../Directory/TestConcurrentFacetedIndexing.cs  |  228 +++
 .../Directory/TestDirectoryTaxonomyReader.cs    |  624 ++++++++
 .../Directory/TestDirectoryTaxonomyWriter.cs    |  588 ++++++++
 .../Facet/Taxonomy/TestCachedOrdinalsReader.cs  |  116 ++
 .../core/Facet/Taxonomy/TestFacetLabel.cs       |  360 +++++
 .../core/Facet/Taxonomy/TestLRUHashMap.cs       |   65 +
 .../Taxonomy/TestSearcherTaxonomyManager.cs     |  398 ++++++
 .../core/Facet/Taxonomy/TestTaxonomyCombined.cs | 1186 ++++++++++++++++
 .../Taxonomy/TestTaxonomyFacetAssociations.cs   |  278 ++++
 .../Facet/Taxonomy/TestTaxonomyFacetCounts.cs   |  851 +++++++++++
 .../Facet/Taxonomy/TestTaxonomyFacetCounts2.cs  |  403 ++++++
 .../Taxonomy/TestTaxonomyFacetSumValueSource.cs |  610 ++++++++
 .../Taxonomy/WriterCache/TestCharBlockArray.cs  |  110 ++
 .../WriterCache/TestCompactLabelToOrdinal.cs    |  151 ++
 .../core/Facet/TestDrillDownQuery.cs            |  282 ++++
 .../core/Facet/TestDrillSideways.cs             | 1332 ++++++++++++++++++
 .../core/Facet/TestFacetsConfig.cs              |  131 ++
 .../core/Facet/TestMultipleIndexFields.cs       |  300 ++++
 .../Facet/TestRandomSamplingFacetsCollector.cs  |  154 ++
 109 files changed, 23872 insertions(+), 38 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/DrillDownQuery.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/DrillDownQuery.cs b/Lucene.Net.Facet/DrillDownQuery.cs
new file mode 100644
index 0000000..f49bb8f
--- /dev/null
+++ b/Lucene.Net.Facet/DrillDownQuery.cs
@@ -0,0 +1,388 @@
+using System;
+using System.Diagnostics;
+using System.Collections.Generic;
+using System.Linq;
+using Lucene.Net.Support;
+
+namespace Lucene.Net.Facet
+{
+
+    /*
+     * 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 IndexReader = Lucene.Net.Index.IndexReader;
+    using Term = Lucene.Net.Index.Term;
+    using Occur = Lucene.Net.Search.BooleanClause.Occur;
+    using BooleanClause = Lucene.Net.Search.BooleanClause;
+    using BooleanQuery = Lucene.Net.Search.BooleanQuery;
+    using ConstantScoreQuery = Lucene.Net.Search.ConstantScoreQuery;
+    using Filter = Lucene.Net.Search.Filter;
+    using FilteredQuery = Lucene.Net.Search.FilteredQuery;
+    using MatchAllDocsQuery = Lucene.Net.Search.MatchAllDocsQuery;
+    using Query = Lucene.Net.Search.Query;
+    using TermQuery = Lucene.Net.Search.TermQuery;
+
+    /// <summary>
+    /// A <seealso cref="Query"/> for drill-down over facet categories. You
+    /// should call <seealso cref="#add(String, String...)"/> for every group of categories you
+    /// want to drill-down over.
+    /// <para>
+    /// <b>NOTE:</b> if you choose to create your own <seealso cref="Query"/> by calling
+    /// <seealso cref="#term"/>, it is recommended to wrap it with <seealso cref="ConstantScoreQuery"/>
+    /// and set the <seealso cref="ConstantScoreQuery#setBoost(float) boost"/> to {@code 0.0f},
+    /// so that it does not affect the scores of the documents.
+    /// 
+    /// @lucene.experimental
+    /// </para>
+    /// </summary>
+    public sealed class DrillDownQuery : Query
+    {
+
+        /// <summary>
+        /// Creates a drill-down term. </summary>
+        public static Term Term(string field, string dim, params string[] path)
+        {
+            return new Term(field, FacetsConfig.PathToString(dim, path));
+        }
+
+        private readonly FacetsConfig config;
+        private readonly BooleanQuery query;
+        private readonly IDictionary<string, int?> drillDownDims = new Dictionary<string, int?>();
+
+        /// <summary>
+        /// Used by clone() </summary>
+        internal DrillDownQuery(FacetsConfig config, BooleanQuery query, IDictionary<string, int?> drillDownDims)
+        {
+            this.query = (BooleanQuery) query.Clone();
+            this.drillDownDims.AddAll(drillDownDims);
+            this.config = config;
+        }
+
+        /// <summary>
+        /// Used by DrillSideways </summary>
+        internal DrillDownQuery(FacetsConfig config, Filter filter, DrillDownQuery other)
+        {
+            query = new BooleanQuery(true); // disable coord
+
+            BooleanClause[] clauses = other.query.Clauses;
+            if (clauses.Length == other.drillDownDims.Count)
+            {
+                throw new System.ArgumentException("cannot apply filter unless baseQuery isn't null; pass ConstantScoreQuery instead");
+            }
+            Debug.Assert(clauses.Length == 1 + other.drillDownDims.Count, clauses.Length + " vs " + (1 + other.drillDownDims.Count));
+            drillDownDims.AddAll(other.drillDownDims);
+            query.Add(new FilteredQuery(clauses[0].Query, filter), Occur.MUST);
+            for (int i = 1; i < clauses.Length; i++)
+            {
+                query.Add(clauses[i].Query, Occur.MUST);
+            }
+            this.config = config;
+        }
+
+        /// <summary>
+        /// Used by DrillSideways </summary>
+        internal DrillDownQuery(FacetsConfig config, Query baseQuery, IList<Query> clauses, IDictionary<string, int?> drillDownDims)
+        {
+            query = new BooleanQuery(true);
+            if (baseQuery != null)
+            {
+                query.Add(baseQuery, Occur.MUST);
+            }
+            foreach (Query clause in clauses)
+            {
+                query.Add(clause, Occur.MUST);
+            }
+            this.drillDownDims.AddAll(drillDownDims);
+            this.config = config;
+        }
+
+        /// <summary>
+        /// Creates a new {@code DrillDownQuery} without a base query, 
+        ///  to perform a pure browsing query (equivalent to using
+        ///  <seealso cref="MatchAllDocsQuery"/> as base). 
+        /// </summary>
+        public DrillDownQuery(FacetsConfig config)
+            : this(config, null)
+        {
+        }
+
+        /// <summary>
+        /// Creates a new {@code DrillDownQuery} over the given base query. Can be
+        ///  {@code null}, in which case the result <seealso cref="Query"/> from
+        ///  <seealso cref="#rewrite(IndexReader)"/> will be a pure browsing query, filtering on
+        ///  the added categories only. 
+        /// </summary>
+        public DrillDownQuery(FacetsConfig config, Query baseQuery)
+        {
+            query = new BooleanQuery(true); // disable coord
+            if (baseQuery != null)
+            {
+                query.Add(baseQuery, Occur.MUST);
+            }
+            this.config = config;
+        }
+
+        /// <summary>
+        /// Merges (ORs) a new path into an existing AND'd
+        ///  clause. 
+        /// </summary>
+        private void Merge(string dim, string[] path)
+        {
+            int index = 0;
+            int? idx;
+            if (drillDownDims.TryGetValue(dim, out idx) && idx.HasValue)
+            {
+                index = idx.Value;
+            }
+
+            if (query.Clauses.Length == drillDownDims.Count + 1)
+            {
+                index++;
+            }
+            ConstantScoreQuery q = (ConstantScoreQuery)query.Clauses[index].Query;
+            if ((q.Query is BooleanQuery) == false)
+            {
+                // App called .add(dim, customQuery) and then tried to
+                // merge a facet label in:
+                throw new Exception("cannot merge with custom Query");
+            }
+            string indexedField = config.GetDimConfig(dim).indexFieldName;
+
+            BooleanQuery bq = (BooleanQuery)q.Query;
+            bq.Add(new TermQuery(Term(indexedField, dim, path)), Occur.SHOULD);
+        }
+
+        /// <summary>
+        /// Adds one dimension of drill downs; if you pass the same
+        ///  dimension more than once it is OR'd with the previous
+        ///  cofnstraints on that dimension, and all dimensions are
+        ///  AND'd against each other and the base query. 
+        /// </summary>
+        public void Add(string dim, params string[] path)
+        {
+
+            if (drillDownDims.ContainsKey(dim))
+            {
+                Merge(dim, path);
+                return;
+            }
+            string indexedField = config.GetDimConfig(dim).indexFieldName;
+
+            BooleanQuery bq = new BooleanQuery(true); // disable coord
+            bq.Add(new TermQuery(Term(indexedField, dim, path)), Occur.SHOULD);
+
+            Add(dim, bq);
+        }
+
+        /// <summary>
+        /// Expert: add a custom drill-down subQuery.  Use this
+        ///  when you have a separate way to drill-down on the
+        ///  dimension than the indexed facet ordinals. 
+        /// </summary>
+        public void Add(string dim, Query subQuery)
+        {
+
+            if (drillDownDims.ContainsKey(dim))
+            {
+                throw new System.ArgumentException("dimension \"" + dim + "\" already has a drill-down");
+            }
+            // TODO: we should use FilteredQuery?
+
+            // So scores of the drill-down query don't have an
+            // effect:
+            ConstantScoreQuery drillDownQuery = new ConstantScoreQuery(subQuery);
+            drillDownQuery.Boost = 0.0f;
+
+            query.Add(drillDownQuery, Occur.MUST);
+
+            drillDownDims[dim] = drillDownDims.Count;
+        }
+
+        /// <summary>
+        /// Expert: add a custom drill-down Filter, e.g. when
+        ///  drilling down after range faceting. 
+        /// </summary>
+        public void Add(string dim, Filter subFilter)
+        {
+
+            if (drillDownDims.ContainsKey(dim))
+            {
+                throw new System.ArgumentException("dimension \"" + dim + "\" already has a drill-down");
+            }
+
+            // TODO: we should use FilteredQuery?
+
+            // So scores of the drill-down query don't have an
+            // effect:
+            ConstantScoreQuery drillDownQuery = new ConstantScoreQuery(subFilter);
+            drillDownQuery.Boost = 0.0f;
+
+            query.Add(drillDownQuery, Occur.MUST);
+
+            drillDownDims[dim] = drillDownDims.Count;
+        }
+
+        internal static Filter GetFilter(Query query)
+        {
+            var scoreQuery = query as ConstantScoreQuery;
+            if (scoreQuery != null)
+            {
+                ConstantScoreQuery csq = scoreQuery;
+                Filter filter = csq.Filter;
+                if (filter != null)
+                {
+                    return filter;
+                }
+                else
+                {
+                    return GetFilter(csq.Query);
+                }
+            }
+            else
+            {
+                return null;
+            }
+        }
+
+        public override object Clone()
+        {
+            return new DrillDownQuery(config, query, drillDownDims);
+        }
+
+        public override int GetHashCode()
+        {
+            const int prime = 31;
+            int result = base.GetHashCode();
+            return prime * result + query.GetHashCode();
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (!(obj is DrillDownQuery))
+            {
+                return false;
+            }
+
+            DrillDownQuery other = (DrillDownQuery)obj;
+            return query.Equals(other.query) && base.Equals(other);
+        }
+
+        public override Query Rewrite(IndexReader r)
+        {
+            if (!query.Clauses.Any())
+            {
+                return new MatchAllDocsQuery();
+            }
+
+            IList<Filter> filters = new List<Filter>();
+            IList<Query> queries = new List<Query>();
+            IList<BooleanClause> clauses = query.Clauses;
+            Query baseQuery;
+            int startIndex;
+            if (drillDownDims.Count == query.Clauses.Count())
+            {
+                baseQuery = new MatchAllDocsQuery();
+                startIndex = 0;
+            }
+            else
+            {
+                baseQuery = clauses[0].Query;
+                startIndex = 1;
+            }
+
+            for (int i = startIndex; i < clauses.Count; i++)
+            {
+                BooleanClause clause = clauses[i];
+                Query queryClause = clause.Query;
+                Filter filter = GetFilter(queryClause);
+                if (filter != null)
+                {
+                    filters.Add(filter);
+                }
+                else
+                {
+                    queries.Add(queryClause);
+                }
+            }
+
+            if (filters.Count == 0)
+            {
+                return query;
+            }
+            else
+            {
+                // Wrap all filters using FilteredQuery
+
+                // TODO: this is hackish; we need to do it because
+                // BooleanQuery can't be trusted to handle the
+                // "expensive filter" case.  Really, each Filter should
+                // know its cost and we should take that more
+                // carefully into account when picking the right
+                // strategy/optimization:
+                Query wrapped;
+                if (queries.Count == 0)
+                {
+                    wrapped = baseQuery;
+                }
+                else
+                {
+                    // disable coord
+                    BooleanQuery wrappedBQ = new BooleanQuery(true);
+                    if ((baseQuery is MatchAllDocsQuery) == false)
+                    {
+                        wrappedBQ.Add(baseQuery, Occur.MUST);
+                    }
+                    foreach (Query q in queries)
+                    {
+                        wrappedBQ.Add(q, Occur.MUST);
+                    }
+                    wrapped = wrappedBQ;
+                }
+
+                foreach (Filter filter in filters)
+                {
+                    wrapped = new FilteredQuery(wrapped, filter, FilteredQuery.QUERY_FIRST_FILTER_STRATEGY);
+                }
+
+                return wrapped;
+            }
+        }
+
+        public override string ToString(string field)
+        {
+            return query.ToString(field);
+        }
+
+        internal BooleanQuery BooleanQuery
+        {
+            get
+            {
+                return query;
+            }
+        }
+
+        internal IDictionary<string, int?> Dims
+        {
+            get
+            {
+                return drillDownDims;
+            }
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/DrillSideways.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/DrillSideways.cs b/Lucene.Net.Facet/DrillSideways.cs
new file mode 100644
index 0000000..5ba8f91
--- /dev/null
+++ b/Lucene.Net.Facet/DrillSideways.cs
@@ -0,0 +1,295 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using Lucene.Net.Search;
+using Lucene.Net.Facet;
+using Lucene.Net.Facet.SortedSet;
+using Lucene.Net.Facet.Taxonomy;
+
+namespace Lucene.Net.Facet
+{
+
+    /*
+     * 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.
+     */
+    /// <summary>
+    /// Computes drill down and sideways counts for the provided
+    /// <seealso cref="DrillDownQuery"/>.  Drill sideways counts include
+    /// alternative values/aggregates for the drill-down
+    /// dimensions so that a dimension does not disappear after
+    /// the user drills down into it.
+    /// 
+    /// <para> Use one of the static search
+    /// methods to do the search, and then get the hits and facet
+    /// results from the returned <seealso cref="DrillSidewaysResult"/>.
+    /// 
+    /// </para>
+    /// <para><b>NOTE</b>: this allocates one {@link
+    /// FacetsCollector} for each drill-down, plus one.  If your
+    /// index has high number of facet labels then this will
+    /// multiply your memory usage.
+    /// 
+    /// @lucene.experimental
+    /// </para>
+    /// </summary>
+    public class DrillSideways
+    {
+
+        /// <summary>
+        /// <seealso cref="IndexSearcher"/> passed to constructor. </summary>
+        protected internal readonly IndexSearcher searcher;
+
+        /// <summary>
+        /// <seealso cref="TaxonomyReader"/> passed to constructor. </summary>
+        protected internal readonly TaxonomyReader taxoReader;
+
+        /// <summary>
+        /// <seealso cref="SortedSetDocValuesReaderState"/> passed to
+        ///  constructor; can be null. 
+        /// </summary>
+        protected internal readonly SortedSetDocValuesReaderState state;
+
+        /// <summary>
+        /// <seealso cref="FacetsConfig"/> passed to constructor. </summary>
+        protected internal readonly FacetsConfig config;
+
+        /// <summary>
+        /// Create a new {@code DrillSideways} instance. </summary>
+        public DrillSideways(IndexSearcher searcher, FacetsConfig config, TaxonomyReader taxoReader)
+            : this(searcher, config, taxoReader, null)
+        {
+        }
+
+        /// <summary>
+        /// Create a new {@code DrillSideways} instance, assuming the categories were
+        ///  indexed with <seealso cref="SortedSetDocValuesFacetField"/>. 
+        /// </summary>
+        public DrillSideways(IndexSearcher searcher, FacetsConfig config, SortedSetDocValuesReaderState state)
+            : this(searcher, config, null, state)
+        {
+        }
+
+        /// <summary>
+        /// Create a new {@code DrillSideways} instance, where some
+        ///  dimensions were indexed with {@link
+        ///  SortedSetDocValuesFacetField} and others were indexed
+        ///  with <seealso cref="FacetField"/>. 
+        /// </summary>
+        public DrillSideways(IndexSearcher searcher, FacetsConfig config, TaxonomyReader taxoReader, SortedSetDocValuesReaderState state)
+        {
+            this.searcher = searcher;
+            this.config = config;
+            this.taxoReader = taxoReader;
+            this.state = state;
+        }
+
+        
+        /// <summary>
+        /// Subclass can override to customize per-dim Facets
+        ///  impl. 
+        /// </summary>
+        protected virtual Facets BuildFacetsResult(FacetsCollector drillDowns, FacetsCollector[] drillSideways, string[] drillSidewaysDims)
+        {
+
+            Facets drillDownFacets;
+            var drillSidewaysFacets = new Dictionary<string, Facets>();
+
+            if (taxoReader != null)
+            {
+                drillDownFacets = new FastTaxonomyFacetCounts(taxoReader, config, drillDowns);
+                if (drillSideways != null)
+                {
+                    for (int i = 0; i < drillSideways.Length; i++)
+                    {
+                        drillSidewaysFacets[drillSidewaysDims[i]] = new FastTaxonomyFacetCounts(taxoReader, config, drillSideways[i]);
+                    }
+                }
+            }
+            else
+            {
+                drillDownFacets = new SortedSetDocValuesFacetCounts(state, drillDowns);
+                if (drillSideways != null)
+                {
+                    for (int i = 0; i < drillSideways.Length; i++)
+                    {
+                        drillSidewaysFacets[drillSidewaysDims[i]] = new SortedSetDocValuesFacetCounts(state, drillSideways[i]);
+                    }
+                }
+            }
+
+            if (drillSidewaysFacets.Count == 0)
+            {
+                return drillDownFacets;
+            }
+            else
+            {
+                return new MultiFacets(drillSidewaysFacets, drillDownFacets);
+            }
+        }
+
+        /// <summary>
+        /// Search, collecting hits with a <seealso cref="Collector"/>, and
+        /// computing drill down and sideways counts.
+        /// </summary>
+        public virtual DrillSidewaysResult Search(DrillDownQuery query, Collector hitCollector)
+        {
+
+            IDictionary<string, int?> drillDownDims = query.Dims;
+
+            FacetsCollector drillDownCollector = new FacetsCollector();
+
+            if (drillDownDims.Count == 0)
+            {
+                // There are no drill-down dims, so there is no
+                // drill-sideways to compute:
+                searcher.Search(query, MultiCollector.Wrap(hitCollector, drillDownCollector));
+                return new DrillSidewaysResult(BuildFacetsResult(drillDownCollector, null, null), null);
+            }
+
+            BooleanQuery ddq = query.BooleanQuery;
+            BooleanClause[] clauses = ddq.Clauses;
+
+            Query baseQuery;
+            int startClause;
+            if (clauses.Length == drillDownDims.Count)
+            {
+                // TODO: we could optimize this pure-browse case by
+                // making a custom scorer instead:
+                baseQuery = new MatchAllDocsQuery();
+                startClause = 0;
+            }
+            else
+            {
+                Debug.Assert(clauses.Length == 1 + drillDownDims.Count);
+                baseQuery = clauses[0].Query;
+                startClause = 1;
+            }
+
+            FacetsCollector[] drillSidewaysCollectors = new FacetsCollector[drillDownDims.Count];
+            for (int i = 0; i < drillSidewaysCollectors.Length; i++)
+            {
+                drillSidewaysCollectors[i] = new FacetsCollector();
+            }
+
+            Query[] drillDownQueries = new Query[clauses.Length - startClause];
+            for (int i = startClause; i < clauses.Length; i++)
+            {
+                drillDownQueries[i - startClause] = clauses[i].Query;
+            }
+            DrillSidewaysQuery dsq = new DrillSidewaysQuery(baseQuery, drillDownCollector, drillSidewaysCollectors, drillDownQueries, ScoreSubDocsAtOnce());
+            searcher.Search(dsq, hitCollector);
+
+            return new DrillSidewaysResult(BuildFacetsResult(drillDownCollector, drillSidewaysCollectors, drillDownDims.Keys.ToArray()), null);
+        }
+
+        /// <summary>
+        /// Search, sorting by <seealso cref="Sort"/>, and computing
+        /// drill down and sideways counts.
+        /// </summary>
+        public virtual DrillSidewaysResult Search(DrillDownQuery query, Filter filter, FieldDoc after, int topN, Sort sort, bool doDocScores, bool doMaxScore)
+        {
+            if (filter != null)
+            {
+                query = new DrillDownQuery(config, filter, query);
+            }
+            if (sort != null)
+            {
+                int limit = searcher.IndexReader.MaxDoc;
+                if (limit == 0)
+                {
+                    limit = 1; // the collector does not alow numHits = 0
+                }
+                topN = Math.Min(topN, limit);
+                TopFieldCollector hitCollector = TopFieldCollector.Create(sort, topN, after, true, doDocScores, doMaxScore, true);
+                DrillSidewaysResult r = Search(query, hitCollector);
+                return new DrillSidewaysResult(r.Facets, hitCollector.TopDocs());
+            }
+            else
+            {
+                return Search(after, query, topN);
+            }
+        }
+
+        /// <summary>
+        /// Search, sorting by score, and computing
+        /// drill down and sideways counts.
+        /// </summary>
+        public virtual DrillSidewaysResult Search(DrillDownQuery query, int topN)
+        {
+            return Search(null, query, topN);
+        }
+
+        /// <summary>
+        /// Search, sorting by score, and computing
+        /// drill down and sideways counts.
+        /// </summary>
+        public virtual DrillSidewaysResult Search(ScoreDoc after, DrillDownQuery query, int topN)
+        {
+            int limit = searcher.IndexReader.MaxDoc;
+            if (limit == 0)
+            {
+                limit = 1; // the collector does not alow numHits = 0
+            }
+            topN = Math.Min(topN, limit);
+            TopScoreDocCollector hitCollector = TopScoreDocCollector.Create(topN, after, true);
+            DrillSidewaysResult r = Search(query, hitCollector);
+            return new DrillSidewaysResult(r.Facets, hitCollector.TopDocs());
+        }
+
+        /// <summary>
+        /// Override this and return true if your collector
+        ///  (e.g., {@code ToParentBlockJoinCollector}) expects all
+        ///  sub-scorers to be positioned on the document being
+        ///  collected.  This will cause some performance loss;
+        ///  default is false.  Note that if you return true from
+        ///  this method (in a subclass) be sure your collector
+        ///  also returns false from {@link
+        ///  Collector#acceptsDocsOutOfOrder}: this will trick
+        ///  {@code BooleanQuery} into also scoring all subDocs at
+        ///  once. 
+        /// </summary>
+        protected virtual bool ScoreSubDocsAtOnce()
+        {
+            return false;
+        }
+
+        /// <summary>
+        /// Result of a drill sideways search, including the
+        ///  <seealso crTopDocsetss"/> and <seealso cref="Lucene"/>. 
+        /// </summary>
+        public class DrillSidewaysResult
+        {
+            /// <summary>
+            /// Combined drill down & sideways results. </summary>
+            public readonly Facets Facets;
+
+            /// <summary>
+            /// Hits. </summary>
+            public readonly TopDocs Hits;
+
+            /// <summary>
+            /// Sole constructor. </summary>
+            public DrillSidewaysResult(Facets facets, TopDocs hits)
+            {
+                this.Facets = facets;
+                this.Hits = hits;
+            }
+        }
+    }
+
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/DrillSidewaysQuery.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/DrillSidewaysQuery.cs b/Lucene.Net.Facet/DrillSidewaysQuery.cs
new file mode 100644
index 0000000..c8e782e
--- /dev/null
+++ b/Lucene.Net.Facet/DrillSidewaysQuery.cs
@@ -0,0 +1,316 @@
+using System;
+using Lucene.Net.Support;
+
+namespace Lucene.Net.Facet
+{
+
+    /*
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements.  See the NOTICE file distributed with
+     * this work for additional information regarding copyright ownership.
+     * The ASF licenses this file to You under the Apache License, Version 2.0
+     * (the "License"); you may not use this file except in compliance with
+     * the License.  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     */
+
+    using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext;
+    using IndexReader = Lucene.Net.Index.IndexReader;
+    using Collector = Lucene.Net.Search.Collector;
+    using DocIdSet = Lucene.Net.Search.DocIdSet;
+    using DocIdSetIterator = Lucene.Net.Search.DocIdSetIterator;
+    using Explanation = Lucene.Net.Search.Explanation;
+    using Filter = Lucene.Net.Search.Filter;
+    using IndexSearcher = Lucene.Net.Search.IndexSearcher;
+    using Query = Lucene.Net.Search.Query;
+    using Scorer = Lucene.Net.Search.Scorer;
+    using BulkScorer = Lucene.Net.Search.BulkScorer;
+    using Weight = Lucene.Net.Search.Weight;
+    using Bits = Lucene.Net.Util.Bits;
+
+    /// <summary>
+    /// Only purpose is to punch through and return a
+    ///  DrillSidewaysScorer 
+    /// </summary>
+
+    internal class DrillSidewaysQuery : Query
+    {
+        internal readonly Query baseQuery;
+        internal readonly Collector drillDownCollector;
+        internal readonly Collector[] drillSidewaysCollectors;
+        internal readonly Query[] drillDownQueries;
+        internal readonly bool scoreSubDocsAtOnce;
+
+        internal DrillSidewaysQuery(Query baseQuery, Collector drillDownCollector, Collector[] drillSidewaysCollectors, Query[] drillDownQueries, bool scoreSubDocsAtOnce)
+        {
+            this.baseQuery = baseQuery;
+            this.drillDownCollector = drillDownCollector;
+            this.drillSidewaysCollectors = drillSidewaysCollectors;
+            this.drillDownQueries = drillDownQueries;
+            this.scoreSubDocsAtOnce = scoreSubDocsAtOnce;
+        }
+
+        public override string ToString(string field)
+        {
+            return "DrillSidewaysQuery";
+        }
+
+        public override Query Rewrite(IndexReader reader)
+        {
+            Query newQuery = baseQuery;
+            while (true)
+            {
+                Query rewrittenQuery = newQuery.Rewrite(reader);
+                if (rewrittenQuery == newQuery)
+                {
+                    break;
+                }
+                newQuery = rewrittenQuery;
+            }
+            if (newQuery == baseQuery)
+            {
+                return this;
+            }
+            else
+            {
+                return new DrillSidewaysQuery(newQuery, drillDownCollector, drillSidewaysCollectors, drillDownQueries, scoreSubDocsAtOnce);
+            }
+        }
+
+        public override Weight CreateWeight(IndexSearcher searcher)
+        {
+            Weight baseWeight = baseQuery.CreateWeight(searcher);
+            object[] drillDowns = new object[drillDownQueries.Length];
+            for (int dim = 0; dim < drillDownQueries.Length; dim++)
+            {
+                Query query = drillDownQueries[dim];
+                Filter filter = DrillDownQuery.GetFilter(query);
+                if (filter != null)
+                {
+                    drillDowns[dim] = filter;
+                }
+                else
+                {
+                    // TODO: would be nice if we could say "we will do no
+                    // scoring" here....
+                    drillDowns[dim] = searcher.Rewrite(query).CreateWeight(searcher);
+                }
+            }
+
+            return new WeightAnonymousInnerClassHelper(this, baseWeight, drillDowns);
+        }
+
+        private class WeightAnonymousInnerClassHelper : Weight
+        {
+            private readonly DrillSidewaysQuery outerInstance;
+
+            private Weight baseWeight;
+            private object[] drillDowns;
+
+            public WeightAnonymousInnerClassHelper(DrillSidewaysQuery outerInstance, Weight baseWeight, object[] drillDowns)
+            {
+                this.outerInstance = outerInstance;
+                this.baseWeight = baseWeight;
+                this.drillDowns = drillDowns;
+            }
+
+            public override Explanation Explain(AtomicReaderContext context, int doc)
+            {
+                return baseWeight.Explain(context, doc);
+            }
+
+            public override Query Query
+            {
+                get
+                {
+                    return outerInstance.baseQuery;
+                }
+            }
+
+            public override float ValueForNormalization
+            {
+                get
+                {
+                    return baseWeight.ValueForNormalization;
+                }
+            }
+
+            public override void Normalize(float norm, float topLevelBoost)
+            {
+                baseWeight.Normalize(norm, topLevelBoost);
+            }
+
+            public override bool ScoresDocsOutOfOrder()
+            {
+                // TODO: would be nice if AssertingIndexSearcher
+                // confirmed this for us
+                return false;
+            }
+
+            public override Scorer Scorer(AtomicReaderContext context, Bits acceptDocs)
+            {
+                // We can only run as a top scorer:
+                throw new System.NotSupportedException();
+            }
+
+            public override BulkScorer BulkScorer(AtomicReaderContext context, bool scoreDocsInOrder, Bits acceptDocs)
+            {
+
+                // TODO: it could be better if we take acceptDocs
+                // into account instead of baseScorer?
+                Scorer baseScorer = baseWeight.Scorer(context, acceptDocs);
+
+                DrillSidewaysScorer.DocsAndCost[] dims = new DrillSidewaysScorer.DocsAndCost[drillDowns.Length];
+                int nullCount = 0;
+                for (int dim = 0; dim < dims.Length; dim++)
+                {
+                    dims[dim] = new DrillSidewaysScorer.DocsAndCost();
+                    dims[dim].sidewaysCollector = outerInstance.drillSidewaysCollectors[dim];
+                    if (drillDowns[dim] is Filter)
+                    {
+                        // Pass null for acceptDocs because we already
+                        // passed it to baseScorer and baseScorer is
+                        // MUST'd here
+                        DocIdSet dis = ((Filter)drillDowns[dim]).GetDocIdSet(context, null);
+
+                        if (dis == null)
+                        {
+                            continue;
+                        }
+
+                        Bits bits = dis.GetBits();
+
+                        if (bits != null)
+                        {
+                            // TODO: this logic is too naive: the
+                            // existence of bits() in DIS today means
+                            // either "I'm a cheap FixedBitSet so apply me down
+                            // low as you decode the postings" or "I'm so
+                            // horribly expensive so apply me after all
+                            // other Query/Filter clauses pass"
+
+                            // Filter supports random access; use that to
+                            // prevent .advance() on costly filters:
+                            dims[dim].bits = bits;
+
+                            // TODO: Filter needs to express its expected
+                            // cost somehow, before pulling the iterator;
+                            // we should use that here to set the order to
+                            // check the filters:
+
+                        }
+                        else
+                        {
+                            DocIdSetIterator disi = dis.GetIterator();
+                            if (disi == null)
+                            {
+                                nullCount++;
+                                continue;
+                            }
+                            dims[dim].disi = disi;
+                        }
+                    }
+                    else
+                    {
+                        DocIdSetIterator disi = ((Weight)drillDowns[dim]).Scorer(context, null);
+                        if (disi == null)
+                        {
+                            nullCount++;
+                            continue;
+                        }
+                        dims[dim].disi = disi;
+                    }
+                }
+
+                // If more than one dim has no matches, then there
+                // are no hits nor drill-sideways counts.  Or, if we
+                // have only one dim and that dim has no matches,
+                // same thing.
+                //if (nullCount > 1 || (nullCount == 1 && dims.length == 1)) {
+                if (nullCount > 1)
+                {
+                    return null;
+                }
+
+                // Sort drill-downs by most restrictive first:
+                Array.Sort(dims);
+
+                if (baseScorer == null)
+                {
+                    return null;
+                }
+
+                return new DrillSidewaysScorer(context, baseScorer, outerInstance.drillDownCollector, dims, outerInstance.scoreSubDocsAtOnce);
+            }
+        }
+
+        // TODO: these should do "deeper" equals/hash on the 2-D drillDownTerms array
+
+        public override int GetHashCode()
+        {
+            const int prime = 31;
+            int result = base.GetHashCode();
+            result = prime * result + ((baseQuery == null) ? 0 : baseQuery.GetHashCode());
+            result = prime * result + ((drillDownCollector == null) ? 0 : drillDownCollector.GetHashCode());
+            result = prime * result + Arrays.GetHashCode(drillDownQueries);
+            result = prime * result + Arrays.GetHashCode(drillSidewaysCollectors);
+            return result;
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (this == obj)
+            {
+                return true;
+            }
+            if (!base.Equals(obj))
+            {
+                return false;
+            }
+            if (this.GetType() != obj.GetType())
+            {
+                return false;
+            }
+            DrillSidewaysQuery other = (DrillSidewaysQuery)obj;
+            if (baseQuery == null)
+            {
+                if (other.baseQuery != null)
+                {
+                    return false;
+                }
+            }
+            else if (!baseQuery.Equals(other.baseQuery))
+            {
+                return false;
+            }
+            if (drillDownCollector == null)
+            {
+                if (other.drillDownCollector != null)
+                {
+                    return false;
+                }
+            }
+            else if (!drillDownCollector.Equals(other.drillDownCollector))
+            {
+                return false;
+            }
+            if (!Arrays.Equals(drillDownQueries, other.drillDownQueries))
+            {
+                return false;
+            }
+            if (!Arrays.Equals(drillSidewaysCollectors, other.drillSidewaysCollectors))
+            {
+                return false;
+            }
+            return true;
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/DrillSidewaysScorer.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/DrillSidewaysScorer.cs b/Lucene.Net.Facet/DrillSidewaysScorer.cs
new file mode 100644
index 0000000..bbd00c1
--- /dev/null
+++ b/Lucene.Net.Facet/DrillSidewaysScorer.cs
@@ -0,0 +1,826 @@
+using System;
+using System.Diagnostics;
+using System.Collections.Generic;
+using Scorer = Lucene.Net.Search.Scorer;
+
+namespace Lucene.Net.Facet
+{
+
+    /*
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements.  See the NOTICE file distributed with
+     * this work for additional information regarding copyright ownership.
+     * The ASF licenses this file to You under the Apache License, Version 2.0
+     * (the "License"); you may not use this file except in compliance with
+     * the License.  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     */
+
+
+    using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext;
+    using DocsEnum = Lucene.Net.Index.DocsEnum;
+    using Collector = Lucene.Net.Search.Collector;
+    using DocIdSetIterator = Lucene.Net.Search.DocIdSetIterator;
+    using Scorer = Lucene.Net.Search.Scorer;
+    using BulkScorer = Lucene.Net.Search.BulkScorer;
+    using Weight = Lucene.Net.Search.Weight;
+    using Bits = Lucene.Net.Util.Bits;
+    using FixedBitSet = Lucene.Net.Util.FixedBitSet;
+
+    internal class DrillSidewaysScorer : BulkScorer
+    {
+
+        //private static boolean DEBUG = false;
+
+        private readonly Collector drillDownCollector;
+
+        private readonly DocsAndCost[] dims;
+
+        // DrillDown DocsEnums:
+        private readonly Scorer baseScorer;
+
+        private readonly AtomicReaderContext context;
+
+        internal readonly bool scoreSubDocsAtOnce;
+
+        private const int CHUNK = 2048;
+        private static readonly int MASK = CHUNK - 1;
+
+        private int collectDocID = -1;
+        private float collectScore;
+
+        internal DrillSidewaysScorer(AtomicReaderContext context, Scorer baseScorer, Collector drillDownCollector, DocsAndCost[] dims, bool scoreSubDocsAtOnce)
+        {
+            this.dims = dims;
+            this.context = context;
+            this.baseScorer = baseScorer;
+            this.drillDownCollector = drillDownCollector;
+            this.scoreSubDocsAtOnce = scoreSubDocsAtOnce;
+        }
+
+        public override bool Score(Collector collector, int maxDoc)
+        {
+            if (maxDoc != int.MaxValue)
+            {
+                throw new System.ArgumentException("maxDoc must be Integer.MAX_VALUE");
+            }
+            //if (DEBUG) {
+            //  System.out.println("\nscore: reader=" + context.reader());
+            //}
+            //System.out.println("score r=" + context.reader());
+            FakeScorer scorer = new FakeScorer(this);
+            collector.Scorer = scorer;
+            if (drillDownCollector != null)
+            {
+                drillDownCollector.Scorer = scorer;
+                drillDownCollector.NextReader = context;
+            }
+            foreach (DocsAndCost dim in dims)
+            {
+                dim.sidewaysCollector.Scorer = scorer;
+                dim.sidewaysCollector.NextReader = context;
+            }
+
+            // TODO: if we ever allow null baseScorer ... it will
+            // mean we DO score docs out of order ... hmm, or if we
+            // change up the order of the conjuntions below
+            Debug.Assert(baseScorer != null);
+
+            // Position all scorers to their first matching doc:
+            baseScorer.NextDoc();
+            int numBits = 0;
+            foreach (DocsAndCost dim in dims)
+            {
+                if (dim.disi != null)
+                {
+                    dim.disi.NextDoc();
+                }
+                else if (dim.bits != null)
+                {
+                    numBits++;
+                }
+            }
+
+            int numDims = dims.Length;
+
+            Bits[] bits = new Bits[numBits];
+            Collector[] bitsSidewaysCollectors = new Collector[numBits];
+
+            DocIdSetIterator[] disis = new DocIdSetIterator[numDims - numBits];
+            Collector[] sidewaysCollectors = new Collector[numDims - numBits];
+            long drillDownCost = 0;
+            int disiUpto = 0;
+            int bitsUpto = 0;
+            for (int dim = 0; dim < numDims; dim++)
+            {
+                DocIdSetIterator disi = dims[dim].disi;
+                if (dims[dim].bits == null)
+                {
+                    disis[disiUpto] = disi;
+                    sidewaysCollectors[disiUpto] = dims[dim].sidewaysCollector;
+                    disiUpto++;
+                    if (disi != null)
+                    {
+                        drillDownCost += disi.Cost();
+                    }
+                }
+                else
+                {
+                    bits[bitsUpto] = dims[dim].bits;
+                    bitsSidewaysCollectors[bitsUpto] = dims[dim].sidewaysCollector;
+                    bitsUpto++;
+                }
+            }
+
+            long baseQueryCost = baseScorer.Cost();
+
+            /*
+            System.out.println("\nbaseDocID=" + baseScorer.docID() + " est=" + estBaseHitCount);
+            System.out.println("  maxDoc=" + context.reader().maxDoc());
+            System.out.println("  maxCost=" + maxCost);
+            System.out.println("  dims[0].freq=" + dims[0].freq);
+            if (numDims > 1) {
+              System.out.println("  dims[1].freq=" + dims[1].freq);
+            }
+            */
+
+            if (bitsUpto > 0 || scoreSubDocsAtOnce || baseQueryCost < drillDownCost / 10)
+            {
+                //System.out.println("queryFirst: baseScorer=" + baseScorer + " disis.length=" + disis.length + " bits.length=" + bits.length);
+                DoQueryFirstScoring(collector, disis, sidewaysCollectors, bits, bitsSidewaysCollectors);
+            }
+            else if (numDims > 1 && (dims[1].disi == null || dims[1].disi.Cost() < baseQueryCost / 10))
+            {
+                //System.out.println("drillDownAdvance");
+                DoDrillDownAdvanceScoring(collector, disis, sidewaysCollectors);
+            }
+            else
+            {
+                //System.out.println("union");
+                DoUnionScoring(collector, disis, sidewaysCollectors);
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Used when base query is highly constraining vs the
+        ///  drilldowns, or when the docs must be scored at once
+        ///  (i.e., like BooleanScorer2, not BooleanScorer).  In
+        ///  this case we just .next() on base and .advance() on
+        ///  the dim filters. 
+        /// </summary>
+        private void DoQueryFirstScoring(Collector collector, DocIdSetIterator[] disis, Collector[] sidewaysCollectors, Bits[] bits, Collector[] bitsSidewaysCollectors)
+        {
+            //if (DEBUG) {
+            //  System.out.println("  doQueryFirstScoring");
+            //}
+            int docID = baseScorer.DocID();
+
+            while (docID != DocsEnum.NO_MORE_DOCS)
+            {
+                Collector failedCollector = null;
+                for (int i = 0; i < disis.Length; i++)
+                {
+                    // TODO: should we sort this 2nd dimension of
+                    // docsEnums from most frequent to least?
+                    DocIdSetIterator disi = disis[i];
+                    if (disi != null && disi.DocID() < docID)
+                    {
+                        disi.Advance(docID);
+                    }
+                    if (disi == null || disi.DocID() > docID)
+                    {
+                        if (failedCollector != null)
+                        {
+                            // More than one dim fails on this document, so
+                            // it's neither a hit nor a near-miss; move to
+                            // next doc:
+                            docID = baseScorer.NextDoc();
+                            goto nextDocContinue;
+                        }
+                        else
+                        {
+                            failedCollector = sidewaysCollectors[i];
+                        }
+                    }
+                }
+
+                // TODO: for the "non-costly Bits" we really should
+                // have passed them down as acceptDocs, but
+                // unfortunately we cannot distinguish today betwen
+                // "bits() is so costly that you should apply it last"
+                // from "bits() is so cheap that you should apply it
+                // everywhere down low"
+
+                // Fold in Filter Bits last, since they may be costly:
+                for (int i = 0; i < bits.Length; i++)
+                {
+                    if (bits[i].Get(docID) == false)
+                    {
+                        if (failedCollector != null)
+                        {
+                            // More than one dim fails on this document, so
+                            // it's neither a hit nor a near-miss; move to
+                            // next doc:
+                            docID = baseScorer.NextDoc();
+                            goto nextDocContinue;
+                        }
+                        else
+                        {
+                            failedCollector = bitsSidewaysCollectors[i];
+                        }
+                    }
+                }
+
+                collectDocID = docID;
+
+                // TODO: we could score on demand instead since we are
+                // daat here:
+                collectScore = baseScorer.Score();
+
+                if (failedCollector == null)
+                {
+                    // Hit passed all filters, so it's "real":
+                    CollectHit(collector, sidewaysCollectors, bitsSidewaysCollectors);
+                }
+                else
+                {
+                    // Hit missed exactly one filter:
+                    CollectNearMiss(failedCollector);
+                }
+
+                docID = baseScorer.NextDoc();
+            nextDocContinue: ;
+            }
+        nextDocBreak: ;
+        }
+
+        /// <summary>
+        /// Used when drill downs are highly constraining vs
+        ///  baseQuery. 
+        /// </summary>
+        private void DoDrillDownAdvanceScoring(Collector collector, DocIdSetIterator[] disis, Collector[] sidewaysCollectors)
+        {
+            int maxDoc = context.Reader.MaxDoc;
+            int numDims = dims.Length;
+
+            //if (DEBUG) {
+            //  System.out.println("  doDrillDownAdvanceScoring");
+            //}
+
+            // TODO: maybe a class like BS, instead of parallel arrays
+            int[] filledSlots = new int[CHUNK];
+            int[] docIDs = new int[CHUNK];
+            float[] scores = new float[CHUNK];
+            int[] missingDims = new int[CHUNK];
+            int[] counts = new int[CHUNK];
+
+            docIDs[0] = -1;
+            int nextChunkStart = CHUNK;
+
+            FixedBitSet seen = new FixedBitSet(CHUNK);
+
+            while (true)
+            {
+                //if (DEBUG) {
+                //  System.out.println("\ncycle nextChunkStart=" + nextChunkStart + " docIds[0]=" + docIDs[0]);
+                //}
+
+                // First dim:
+                //if (DEBUG) {
+                //  System.out.println("  dim0");
+                //}
+                DocIdSetIterator disi = disis[0];
+                if (disi != null)
+                {
+                    int docID = disi.DocID();
+                    while (docID < nextChunkStart)
+                    {
+                        int slot = docID & MASK;
+
+                        if (docIDs[slot] != docID)
+                        {
+                            seen.Set(slot);
+                            // Mark slot as valid:
+                            //if (DEBUG) {
+                            //  System.out.println("    set docID=" + docID + " id=" + context.reader().document(docID).get("id"));
+                            //}
+                            docIDs[slot] = docID;
+                            missingDims[slot] = 1;
+                            counts[slot] = 1;
+                        }
+
+                        docID = disi.NextDoc();
+                    }
+                }
+
+                // Second dim:
+                //if (DEBUG) {
+                //  System.out.println("  dim1");
+                //}
+                disi = disis[1];
+                if (disi != null)
+                {
+                    int docID = disi.DocID();
+                    while (docID < nextChunkStart)
+                    {
+                        int slot = docID & MASK;
+
+                        if (docIDs[slot] != docID)
+                        {
+                            // Mark slot as valid:
+                            seen.Set(slot);
+                            //if (DEBUG) {
+                            //  System.out.println("    set docID=" + docID + " missingDim=0 id=" + context.reader().document(docID).get("id"));
+                            //}
+                            docIDs[slot] = docID;
+                            missingDims[slot] = 0;
+                            counts[slot] = 1;
+                        }
+                        else
+                        {
+                            // TODO: single-valued dims will always be true
+                            // below; we could somehow specialize
+                            if (missingDims[slot] >= 1)
+                            {
+                                missingDims[slot] = 2;
+                                counts[slot] = 2;
+                                //if (DEBUG) {
+                                //  System.out.println("    set docID=" + docID + " missingDim=2 id=" + context.reader().document(docID).get("id"));
+                                //}
+                            }
+                            else
+                            {
+                                counts[slot] = 1;
+                                //if (DEBUG) {
+                                //  System.out.println("    set docID=" + docID + " missingDim=" + missingDims[slot] + " id=" + context.reader().document(docID).get("id"));
+                                //}
+                            }
+                        }
+
+                        docID = disi.NextDoc();
+                    }
+                }
+
+                // After this we can "upgrade" to conjunction, because
+                // any doc not seen by either dim 0 or dim 1 cannot be
+                // a hit or a near miss:
+
+                //if (DEBUG) {
+                //  System.out.println("  baseScorer");
+                //}
+
+                // Fold in baseScorer, using advance:
+                int filledCount = 0;
+                int slot0 = 0;
+                while (slot0 < CHUNK && (slot0 = seen.NextSetBit(slot0)) != -1)
+                {
+                    int ddDocID = docIDs[slot0];
+                    Debug.Assert(ddDocID != -1);
+
+                    int baseDocID = baseScorer.DocID();
+                    if (baseDocID < ddDocID)
+                    {
+                        baseDocID = baseScorer.Advance(ddDocID);
+                    }
+                    if (baseDocID == ddDocID)
+                    {
+                        //if (DEBUG) {
+                        //  System.out.println("    keep docID=" + ddDocID + " id=" + context.reader().document(ddDocID).get("id"));
+                        //}
+                        scores[slot0] = baseScorer.Score();
+                        filledSlots[filledCount++] = slot0;
+                        counts[slot0]++;
+                    }
+                    else
+                    {
+                        //if (DEBUG) {
+                        //  System.out.println("    no docID=" + ddDocID + " id=" + context.reader().document(ddDocID).get("id"));
+                        //}
+                        docIDs[slot0] = -1;
+
+                        // TODO: we could jump slot0 forward to the
+                        // baseDocID ... but we'd need to set docIDs for
+                        // intervening slots to -1
+                    }
+                    slot0++;
+                }
+                seen.Clear(0, CHUNK);
+
+                if (filledCount == 0)
+                {
+                    if (nextChunkStart >= maxDoc)
+                    {
+                        break;
+                    }
+                    nextChunkStart += CHUNK;
+                    continue;
+                }
+
+                // TODO: factor this out & share w/ union scorer,
+                // except we start from dim=2 instead:
+                for (int dim = 2; dim < numDims; dim++)
+                {
+                    //if (DEBUG) {
+                    //  System.out.println("  dim=" + dim + " [" + dims[dim].dim + "]");
+                    //}
+                    disi = disis[dim];
+                    if (disi != null)
+                    {
+                        int docID = disi.DocID();
+                        while (docID < nextChunkStart)
+                        {
+                            int slot = docID & MASK;
+                            if (docIDs[slot] == docID && counts[slot] >= dim)
+                            {
+                                // TODO: single-valued dims will always be true
+                                // below; we could somehow specialize
+                                if (missingDims[slot] >= dim)
+                                {
+                                    //if (DEBUG) {
+                                    //  System.out.println("    set docID=" + docID + " count=" + (dim+2));
+                                    //}
+                                    missingDims[slot] = dim + 1;
+                                    counts[slot] = dim + 2;
+                                }
+                                else
+                                {
+                                    //if (DEBUG) {
+                                    //  System.out.println("    set docID=" + docID + " missing count=" + (dim+1));
+                                    //}
+                                    counts[slot] = dim + 1;
+                                }
+                            }
+
+                            // TODO: sometimes use advance?
+                            docID = disi.NextDoc();
+                        }
+                    }
+                }
+
+                // Collect:
+                //if (DEBUG) {
+                //  System.out.println("  now collect: " + filledCount + " hits");
+                //}
+                for (int i = 0; i < filledCount; i++)
+                {
+                    int slot = filledSlots[i];
+                    collectDocID = docIDs[slot];
+                    collectScore = scores[slot];
+                    //if (DEBUG) {
+                    //  System.out.println("    docID=" + docIDs[slot] + " count=" + counts[slot]);
+                    //}
+                    if (counts[slot] == 1 + numDims)
+                    {
+                        CollectHit(collector, sidewaysCollectors);
+                    }
+                    else if (counts[slot] == numDims)
+                    {
+                        CollectNearMiss(sidewaysCollectors[missingDims[slot]]);
+                    }
+                }
+
+                if (nextChunkStart >= maxDoc)
+                {
+                    break;
+                }
+
+                nextChunkStart += CHUNK;
+            }
+        }
+
+        private void DoUnionScoring(Collector collector, DocIdSetIterator[] disis, Collector[] sidewaysCollectors)
+        {
+            //if (DEBUG) {
+            //  System.out.println("  doUnionScoring");
+            //}
+
+            int maxDoc = context.Reader.MaxDoc;
+            int numDims = dims.Length;
+
+            // TODO: maybe a class like BS, instead of parallel arrays
+            int[] filledSlots = new int[CHUNK];
+            int[] docIDs = new int[CHUNK];
+            float[] scores = new float[CHUNK];
+            int[] missingDims = new int[CHUNK];
+            int[] counts = new int[CHUNK];
+
+            docIDs[0] = -1;
+
+            // NOTE: this is basically a specialized version of
+            // BooleanScorer, to the minShouldMatch=N-1 case, but
+            // carefully tracking which dimension failed to match
+
+            int nextChunkStart = CHUNK;
+
+            while (true)
+            {
+                //if (DEBUG) {
+                //  System.out.println("\ncycle nextChunkStart=" + nextChunkStart + " docIds[0]=" + docIDs[0]);
+                //}
+                int filledCount = 0;
+                int docID = baseScorer.DocID();
+                //if (DEBUG) {
+                //  System.out.println("  base docID=" + docID);
+                //}
+                while (docID < nextChunkStart)
+                {
+                    int slot = docID & MASK;
+                    //if (DEBUG) {
+                    //  System.out.println("    docIDs[slot=" + slot + "]=" + docID + " id=" + context.reader().document(docID).get("id"));
+                    //}
+
+                    // Mark slot as valid:
+                    Debug.Assert(docIDs[slot] != docID, "slot=" + slot + " docID=" + docID);
+                    docIDs[slot] = docID;
+                    scores[slot] = baseScorer.Score();
+                    filledSlots[filledCount++] = slot;
+                    missingDims[slot] = 0;
+                    counts[slot] = 1;
+
+                    docID = baseScorer.NextDoc();
+                }
+
+                if (filledCount == 0)
+                {
+                    if (nextChunkStart >= maxDoc)
+                    {
+                        break;
+                    }
+                    nextChunkStart += CHUNK;
+                    continue;
+                }
+
+                // First drill-down dim, basically adds SHOULD onto
+                // the baseQuery:
+                //if (DEBUG) {
+                //  System.out.println("  dim=0 [" + dims[0].dim + "]");
+                //}
+                DocIdSetIterator disi = disis[0];
+                if (disi != null)
+                {
+                    docID = disi.DocID();
+                    //if (DEBUG) {
+                    //  System.out.println("    start docID=" + docID);
+                    //}
+                    while (docID < nextChunkStart)
+                    {
+                        int slot = docID & MASK;
+                        if (docIDs[slot] == docID)
+                        {
+                            //if (DEBUG) {
+                            //  System.out.println("      set docID=" + docID + " count=2");
+                            //}
+                            missingDims[slot] = 1;
+                            counts[slot] = 2;
+                        }
+                        docID = disi.NextDoc();
+                    }
+                }
+
+                for (int dim = 1; dim < numDims; dim++)
+                {
+                    //if (DEBUG) {
+                    //  System.out.println("  dim=" + dim + " [" + dims[dim].dim + "]");
+                    //}
+
+                    disi = disis[dim];
+                    if (disi != null)
+                    {
+                        docID = disi.DocID();
+                        //if (DEBUG) {
+                        //  System.out.println("    start docID=" + docID);
+                        //}
+                        while (docID < nextChunkStart)
+                        {
+                            int slot = docID & MASK;
+                            if (docIDs[slot] == docID && counts[slot] >= dim)
+                            {
+                                // This doc is still in the running...
+                                // TODO: single-valued dims will always be true
+                                // below; we could somehow specialize
+                                if (missingDims[slot] >= dim)
+                                {
+                                    //if (DEBUG) {
+                                    //  System.out.println("      set docID=" + docID + " count=" + (dim+2));
+                                    //}
+                                    missingDims[slot] = dim + 1;
+                                    counts[slot] = dim + 2;
+                                }
+                                else
+                                {
+                                    //if (DEBUG) {
+                                    //  System.out.println("      set docID=" + docID + " missing count=" + (dim+1));
+                                    //}
+                                    counts[slot] = dim + 1;
+                                }
+                            }
+                            docID = disi.NextDoc();
+                        }
+                    }
+                }
+
+                // Collect:
+                //System.out.println("  now collect: " + filledCount + " hits");
+                for (int i = 0; i < filledCount; i++)
+                {
+                    // NOTE: This is actually in-order collection,
+                    // because we only accept docs originally returned by
+                    // the baseScorer (ie that Scorer is AND'd)
+                    int slot = filledSlots[i];
+                    collectDocID = docIDs[slot];
+                    collectScore = scores[slot];
+                    //if (DEBUG) {
+                    //  System.out.println("    docID=" + docIDs[slot] + " count=" + counts[slot]);
+                    //}
+                    //System.out.println("  collect doc=" + collectDocID + " main.freq=" + (counts[slot]-1) + " main.doc=" + collectDocID + " exactCount=" + numDims);
+                    if (counts[slot] == 1 + numDims)
+                    {
+                        //System.out.println("    hit");
+                        CollectHit(collector, sidewaysCollectors);
+                    }
+                    else if (counts[slot] == numDims)
+                    {
+                        //System.out.println("    sw");
+                        CollectNearMiss(sidewaysCollectors[missingDims[slot]]);
+                    }
+                }
+
+                if (nextChunkStart >= maxDoc)
+                {
+                    break;
+                }
+
+                nextChunkStart += CHUNK;
+            }
+        }
+
+        private void CollectHit(Collector collector, Collector[] sidewaysCollectors)
+        {
+            //if (DEBUG) {
+            //  System.out.println("      hit");
+            //}
+
+            collector.Collect(collectDocID);
+            if (drillDownCollector != null)
+            {
+                drillDownCollector.Collect(collectDocID);
+            }
+
+            // TODO: we could "fix" faceting of the sideways counts
+            // to do this "union" (of the drill down hits) in the
+            // end instead:
+
+            // Tally sideways counts:
+            for (int dim = 0; dim < sidewaysCollectors.Length; dim++)
+            {
+                sidewaysCollectors[dim].Collect(collectDocID);
+            }
+        }
+
+        private void CollectHit(Collector collector, Collector[] sidewaysCollectors, Collector[] sidewaysCollectors2)
+        {
+            //if (DEBUG) {
+            //  System.out.println("      hit");
+            //}
+
+            collector.Collect(collectDocID);
+            if (drillDownCollector != null)
+            {
+                drillDownCollector.Collect(collectDocID);
+            }
+
+            // TODO: we could "fix" faceting of the sideways counts
+            // to do this "union" (of the drill down hits) in the
+            // end instead:
+
+            // Tally sideways counts:
+            for (int i = 0; i < sidewaysCollectors.Length; i++)
+            {
+                sidewaysCollectors[i].Collect(collectDocID);
+            }
+            for (int i = 0; i < sidewaysCollectors2.Length; i++)
+            {
+                sidewaysCollectors2[i].Collect(collectDocID);
+            }
+        }
+
+        private void CollectNearMiss(Collector sidewaysCollector)
+        {
+            //if (DEBUG) {
+            //  System.out.println("      missingDim=" + dim);
+            //}
+            sidewaysCollector.Collect(collectDocID);
+        }
+
+        private sealed class FakeScorer : Scorer
+        {
+            private readonly DrillSidewaysScorer outerInstance;
+
+            internal float score_Renamed;
+            internal int doc;
+
+            public FakeScorer(DrillSidewaysScorer outerInstance)
+                : base(null)
+            {
+                this.outerInstance = outerInstance;
+            }
+
+            public override int Advance(int target)
+            {
+                throw new System.NotSupportedException("FakeScorer doesn't support advance(int)");
+            }
+
+            public override int DocID()
+            {
+                return outerInstance.collectDocID;
+            }
+
+            public override int Freq()
+            {
+                return 1 + outerInstance.dims.Length;
+            }
+
+            public override int NextDoc()
+            {
+                throw new System.NotSupportedException("FakeScorer doesn't support nextDoc()");
+            }
+
+            public override float Score()
+            {
+                return outerInstance.collectScore;
+            }
+
+            public override long Cost()
+            {
+                return outerInstance.baseScorer.Cost();
+            }
+
+            public override ICollection<ChildScorer> Children
+            {
+                get
+                {
+                    return new[] { new Scorer.ChildScorer(outerInstance.baseScorer, "MUST") };
+                }
+            }
+
+            public override Weight Weight
+            {
+                get
+                {
+                    throw new System.NotSupportedException();
+                }
+            }
+        }
+
+        internal class DocsAndCost : IComparable<DocsAndCost>
+        {
+            // Iterator for docs matching this dim's filter, or ...
+            internal DocIdSetIterator disi;
+            // Random access bits:
+            internal Bits bits;
+            internal Collector sidewaysCollector;
+            internal string dim;
+
+            public virtual int CompareTo(DocsAndCost other)
+            {
+                if (disi == null)
+                {
+                    if (other.disi == null)
+                    {
+                        return 0;
+                    }
+                    else
+                    {
+                        return 1;
+                    }
+                }
+                else if (other.disi == null)
+                {
+                    return -1;
+                }
+                else if (disi.Cost() < other.disi.Cost())
+                {
+                    return -1;
+                }
+                else if (disi.Cost() > other.disi.Cost())
+                {
+                    return 1;
+                }
+                else
+                {
+                    return 0;
+                }
+            }
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/FacetField.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/FacetField.cs b/Lucene.Net.Facet/FacetField.cs
new file mode 100644
index 0000000..7d5c1c5
--- /dev/null
+++ b/Lucene.Net.Facet/FacetField.cs
@@ -0,0 +1,91 @@
+using Lucene.Net.Support;
+
+namespace Lucene.Net.Facet
+{
+
+    /*
+     * 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 Document = Lucene.Net.Documents.Document;
+    using Field = Lucene.Net.Documents.Field;
+    using FieldType = Lucene.Net.Documents.FieldType;
+
+    /// <summary>
+    /// Add an instance of this to your <seealso cref="Document"/> for every facet label.
+    /// 
+    /// <para>
+    /// <b>NOTE:</b> you must call <seealso cref="FacetsConfig#build(Document)"/> before
+    /// you add the document to IndexWriter.
+    /// </para>
+    /// </summary>
+    public class FacetField : Field
+    {
+        internal static readonly FieldType TYPE = new FieldType();
+        static FacetField()
+        {
+            TYPE.Indexed = true;
+            TYPE.Freeze();
+        }
+
+        /// <summary>
+        /// Dimension for this field. </summary>
+        public readonly string dim;
+
+        /// <summary>
+        /// Path for this field. </summary>
+        public readonly string[] path;
+
+        /// <summary>
+        /// Creates the this from {@code dim} and
+        ///  {@code path}. 
+        /// </summary>
+        public FacetField(string dim, params string[] path)
+            : base("dummy", TYPE)
+        {
+            verifyLabel(dim);
+            foreach (string label in path)
+            {
+                verifyLabel(label);
+            }
+            this.dim = dim;
+            if (path.Length == 0)
+            {
+                throw new System.ArgumentException("path must have at least one element");
+            }
+            this.path = path;
+        }
+
+        public override string ToString()
+        {
+            return "FacetField(dim=" + dim + " path=" + Arrays.ToString(path) + ")";
+        }
+
+        /// <summary>
+        /// Verifies the label is not null or empty string.
+        /// 
+        ///  @lucene.internal 
+        /// </summary>
+        public static void verifyLabel(string label)
+        {
+            if (string.IsNullOrEmpty(label))
+            {
+                throw new System.ArgumentException("empty or null components not allowed; got: " + label);
+            }
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/FacetResult.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/FacetResult.cs b/Lucene.Net.Facet/FacetResult.cs
new file mode 100644
index 0000000..f53c841
--- /dev/null
+++ b/Lucene.Net.Facet/FacetResult.cs
@@ -0,0 +1,103 @@
+using System.Text;
+using Lucene.Net.Support;
+
+namespace Lucene.Net.Facet
+{
+
+	/*
+	 * 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.
+	 */
+
+	/// <summary>
+	/// Counts or aggregates for a single dimension. </summary>
+	public sealed class FacetResult
+	{
+
+	  /// <summary>
+	  /// Dimension that was requested. </summary>
+	  public readonly string dim;
+
+	  /// <summary>
+	  /// Path whose children were requested. </summary>
+	  public readonly string[] path;
+
+	  /// <summary>
+	  /// Total value for this path (sum of all child counts, or
+	  ///  sum of all child values), even those not included in
+	  ///  the topN. 
+	  /// </summary>
+	  public readonly float value;
+
+	  /// <summary>
+	  /// How many child labels were encountered. </summary>
+	  public readonly int childCount;
+
+	  /// <summary>
+	  /// Child counts. </summary>
+	  public readonly LabelAndValue[] labelValues;
+
+	  /// <summary>
+	  /// Sole constructor. </summary>
+	  public FacetResult(string dim, string[] path, float value, LabelAndValue[] labelValues, int childCount)
+	  {
+		this.dim = dim;
+		this.path = path;
+		this.value = value;
+		this.labelValues = labelValues;
+		this.childCount = childCount;
+	  }
+
+	  public override string ToString()
+	  {
+		StringBuilder sb = new StringBuilder();
+		sb.Append("dim=");
+		sb.Append(dim);
+		sb.Append(" path=");
+		sb.Append("[" + Arrays.ToString(path) + "]");
+		sb.Append(" value=");
+		sb.Append(value);
+		sb.Append(" childCount=");
+		sb.Append(childCount);
+		sb.Append('\n');
+		foreach (LabelAndValue labelValue in labelValues)
+		{
+		  sb.Append("  " + labelValue + "\n");
+		}
+		return sb.ToString();
+	  }
+
+	  public override bool Equals(object _other)
+	  {
+		if ((_other is FacetResult) == false)
+		{
+		  return false;
+		}
+		FacetResult other = (FacetResult) _other;
+		return value.Equals(other.value) && childCount == other.childCount && Arrays.Equals(labelValues, other.labelValues);
+	  }
+
+	  public override int GetHashCode()
+	  {
+		int hashCode = value.GetHashCode() + 31 * childCount;
+		foreach (LabelAndValue labelValue in labelValues)
+		{
+		  hashCode = labelValue.GetHashCode() + 31 * hashCode;
+		}
+		return hashCode;
+	  }
+	}
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Facets.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Facets.cs b/Lucene.Net.Facet/Facets.cs
new file mode 100644
index 0000000..f046127
--- /dev/null
+++ b/Lucene.Net.Facet/Facets.cs
@@ -0,0 +1,64 @@
+using System.Collections.Generic;
+using Lucene.Net.Support;
+using Lucene.Net.Facet;
+
+namespace Lucene.Net.Facet
+{
+
+    /*
+     * 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.
+     */
+
+
+    /// <summary>
+    /// Common base class for all facets implementations.
+    /// 
+    ///  @lucene.experimental 
+    /// </summary>
+    public abstract class Facets
+    {
+
+        /// <summary>
+        /// Default constructor. </summary>
+        public Facets()
+        {
+        }
+
+        /// <summary>
+        /// Returns the topN child labels under the specified
+        ///  path.  Returns null if the specified path doesn't
+        ///  exist or if this dimension was never seen. 
+        /// </summary>
+        public abstract FacetResult GetTopChildren(int topN, string dim, params string[] path);
+
+        /// <summary>
+        /// Return the count or value
+        ///  for a specific path.  Returns -1 if
+        ///  this path doesn't exist, else the count. 
+        /// </summary>
+        public abstract float GetSpecificValue(string dim, params string[] path);
+
+        /// <summary>
+        /// Returns topN labels for any dimension that had hits,
+        ///  sorted by the number of hits that dimension matched;
+        ///  this is used for "sparse" faceting, where many
+        ///  different dimensions were indexed, for example
+        ///  depending on the type of document. 
+        /// </summary>
+        public abstract IList<FacetResult> GetAllDims(int topN);
+    }
+
+}
\ No newline at end of file


[07/26] lucenenet git commit: first commit of facet porting, failing tests will be fixed in next commits.

Posted by sy...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/Taxonomy/WriterCache/TestCompactLabelToOrdinal.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/WriterCache/TestCompactLabelToOrdinal.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/WriterCache/TestCompactLabelToOrdinal.cs
new file mode 100644
index 0000000..549bf09
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/WriterCache/TestCompactLabelToOrdinal.cs
@@ -0,0 +1,151 @@
+using System;
+using System.Collections.Generic;
+using Lucene.Net.Support;
+using NUnit.Framework;
+
+namespace Lucene.Net.Facet.Taxonomy.WriterCache
+{
+
+
+    using TestUtil = Lucene.Net.Util.TestUtil;
+
+    /*
+     * 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.
+     */
+    [TestFixture]
+    public class TestCompactLabelToOrdinal : FacetTestCase
+    {
+        /* not finished to porting yet because of missing decoder implementation */
+        /*
+        public virtual void TestL2O()
+        {
+            LabelToOrdinal map = new LabelToOrdinalMap();
+
+            CompactLabelToOrdinal compact = new CompactLabelToOrdinal(2000000, 0.15f, 3);
+
+            int n = AtLeast(10 * 1000);
+            const int numUniqueValues = 50 * 1000;
+
+            string[] uniqueValues = new string[numUniqueValues];
+            byte[] buffer = new byte[50];
+
+            Random random = Random();
+            for (int i = 0; i < numUniqueValues; )
+            {
+                random.NextBytes(buffer);
+                int size = 1 + random.Next(buffer.Length);
+
+                // This test is turning random bytes into a string,
+                // this is asking for trouble.
+                CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder().onUnmappableCharacter(CodingErrorAction.REPLACE).onMalformedInput(CodingErrorAction.REPLACE);
+                uniqueValues[i] = decoder.decode(ByteBuffer.Wrap(buffer, 0, size)).ToString();
+                // we cannot have empty path components, so eliminate all prefix as well
+                // as middle consecutive delimiter chars.
+                uniqueValues[i] = uniqueValues[i].replaceAll("/+", "/");
+                if (uniqueValues[i].StartsWith("/", StringComparison.Ordinal))
+                {
+                    uniqueValues[i] = uniqueValues[i].Substring(1);
+                }
+                if (uniqueValues[i].IndexOf(CompactLabelToOrdinal.TERMINATOR_CHAR) == -1)
+                {
+                    i++;
+                }
+            }
+
+            var tmpDir = CreateTempDir("testLableToOrdinal");
+            var f = new File(tmpDir, "CompactLabelToOrdinalTest.tmp");
+            int flushInterval = 10;
+
+            for (int i = 0; i < n; i++)
+            {
+                if (i > 0 && i % flushInterval == 0)
+                {
+                    compact.Flush(f);
+                    compact = CompactLabelToOrdinal.open(f, 0.15f, 3);
+                    Assert.True(f.delete());
+                    if (flushInterval < (n / 10))
+                    {
+                        flushInterval *= 10;
+                    }
+                }
+
+                int index = random.Next(numUniqueValues);
+                FacetLabel label;
+                string s = uniqueValues[index];
+                if (s.Length == 0)
+                {
+                    label = new FacetLabel();
+                }
+                else
+                {
+                    label = new FacetLabel(s.Split("/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries));
+                }
+
+                int ord1 = map.GetOrdinal(label);
+                int ord2 = compact.GetOrdinal(label);
+
+                Assert.AreEqual(ord1, ord2);
+
+                if (ord1 == LabelToOrdinal.INVALID_ORDINAL)
+                {
+                    ord1 = compact.NextOrdinal;
+                    map.AddLabel(label, ord1);
+                    compact.AddLabel(label, ord1);
+                }
+            }
+
+            for (int i = 0; i < numUniqueValues; i++)
+            {
+                FacetLabel label;
+                string s = uniqueValues[i];
+                if (s.Length == 0)
+                {
+                    label = new FacetLabel();
+                }
+                else
+                {
+                    label = new FacetLabel(s.Split("/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries));
+                }
+                int ord1 = map.GetOrdinal(label);
+                int ord2 = compact.GetOrdinal(label);
+                Assert.AreEqual(ord1, ord2);
+            }
+        }
+
+        private class LabelToOrdinalMap : LabelToOrdinal
+        {
+            internal IDictionary<FacetLabel, int?> map = new Dictionary<FacetLabel, int?>();
+
+            internal LabelToOrdinalMap()
+            {
+            }
+
+            public override void AddLabel(FacetLabel label, int ordinal)
+            {
+                map[label] = ordinal;
+            }
+
+            public override int GetOrdinal(FacetLabel label)
+            {
+                int? value = map[label];
+                return (value != null) ? (int)value : LabelToOrdinal.INVALID_ORDINAL;
+            }
+
+        } */
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/TestDrillDownQuery.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/TestDrillDownQuery.cs b/src/Lucene.Net.Tests/core/Facet/TestDrillDownQuery.cs
new file mode 100644
index 0000000..1947b8b
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/TestDrillDownQuery.cs
@@ -0,0 +1,282 @@
+using System;
+using System.Diagnostics;
+using Lucene.Net.Randomized.Generators;
+using NUnit.Framework;
+
+namespace Lucene.Net.Facet
+{
+
+    /*
+     * 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 MockAnalyzer = Lucene.Net.Analysis.MockAnalyzer;
+    using MockTokenizer = Lucene.Net.Analysis.MockTokenizer;
+    using Document = Lucene.Net.Documents.Document;
+    using Field = Lucene.Net.Documents.Field;
+    using TextField = Lucene.Net.Documents.TextField;
+    using TaxonomyWriter = Lucene.Net.Facet.Taxonomy.TaxonomyWriter;
+    using DirectoryTaxonomyReader = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyReader;
+    using DirectoryTaxonomyWriter = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter;
+    using IndexReader = Lucene.Net.Index.IndexReader;
+    using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter;
+    using Term = Lucene.Net.Index.Term;
+    using IndexSearcher = Lucene.Net.Search.IndexSearcher;
+    using MatchAllDocsQuery = Lucene.Net.Search.MatchAllDocsQuery;
+    using Query = Lucene.Net.Search.Query;
+    using QueryUtils = Lucene.Net.Search.QueryUtils;
+    using ScoreDoc = Lucene.Net.Search.ScoreDoc;
+    using TermQuery = Lucene.Net.Search.TermQuery;
+    using TopDocs = Lucene.Net.Search.TopDocs;
+    using Directory = Lucene.Net.Store.Directory;
+    using IOUtils = Lucene.Net.Util.IOUtils;
+
+    [TestFixture]
+    public class TestDrillDownQuery : FacetTestCase
+    {
+
+        private static IndexReader reader;
+        private static DirectoryTaxonomyReader taxo;
+        private static Directory dir;
+        private static Directory taxoDir;
+        private static FacetsConfig config;
+
+      
+        [TestFixtureTearDown]
+        public static void AfterClassDrillDownQueryTest()
+        {
+            IOUtils.Close(reader, taxo, dir, taxoDir);
+            reader = null;
+            taxo = null;
+            dir = null;
+            taxoDir = null;
+            config = null;
+        }
+
+        [TestFixtureSetUp]
+        public static void BeforeClassDrillDownQueryTest()
+        {
+            dir = NewDirectory();
+            Random r = Random();
+            RandomIndexWriter writer = new RandomIndexWriter(r, dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(r, MockTokenizer.KEYWORD, false)));
+
+            taxoDir = NewDirectory();
+            TaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir);
+            config = new FacetsConfig();
+
+            // Randomize the per-dim config:
+            config.SetHierarchical("a", Random().NextBoolean());
+            config.SetMultiValued("a", Random().NextBoolean());
+            if (Random().NextBoolean())
+            {
+                config.SetIndexFieldName("a", "$a");
+            }
+            config.SetRequireDimCount("a", true);
+
+            config.SetHierarchical("b", Random().NextBoolean());
+            config.SetMultiValued("b", Random().NextBoolean());
+            if (Random().NextBoolean())
+            {
+                config.SetIndexFieldName("b", "$b");
+            }
+            config.SetRequireDimCount("b", true);
+
+            for (int i = 0; i < 100; i++)
+            {
+                Document doc = new Document();
+                if (i % 2 == 0) // 50
+                {
+                    doc.Add(new TextField("content", "foo", Field.Store.NO));
+                }
+                if (i % 3 == 0) // 33
+                {
+                    doc.Add(new TextField("content", "bar", Field.Store.NO));
+                }
+                if (i % 4 == 0) // 25
+                {
+                    if (r.NextBoolean())
+                    {
+                        doc.Add(new FacetField("a", "1"));
+                    }
+                    else
+                    {
+                        doc.Add(new FacetField("a", "2"));
+                    }
+                }
+                if (i % 5 == 0) // 20
+                {
+                    doc.Add(new FacetField("b", "1"));
+                }
+                writer.AddDocument(config.Build(taxoWriter, doc));
+            }
+
+            taxoWriter.Dispose();
+            reader = writer.Reader;
+            writer.Dispose();
+
+            taxo = new DirectoryTaxonomyReader(taxoDir);
+        }
+
+        [Test]
+        public virtual void TestAndOrs()
+        {
+            IndexSearcher searcher = NewSearcher(reader);
+
+            // test (a/1 OR a/2) AND b/1
+            DrillDownQuery q = new DrillDownQuery(config);
+            q.Add("a", "1");
+            q.Add("a", "2");
+            q.Add("b", "1");
+            TopDocs docs = searcher.Search(q, 100);
+            Assert.AreEqual(5, docs.TotalHits);
+        }
+
+        [Test]
+        public virtual void TestQuery()
+        {
+            IndexSearcher searcher = NewSearcher(reader);
+
+            // Making sure the query yields 25 documents with the facet "a"
+            DrillDownQuery q = new DrillDownQuery(config);
+            q.Add("a");
+            QueryUtils.Check(q);
+            TopDocs docs = searcher.Search(q, 100);
+            Assert.AreEqual(25, docs.TotalHits);
+
+            // Making sure the query yields 5 documents with the facet "b" and the
+            // previous (facet "a") query as a base query
+            DrillDownQuery q2 = new DrillDownQuery(config, q);
+            q2.Add("b");
+            docs = searcher.Search(q2, 100);
+            Assert.AreEqual(5, docs.TotalHits);
+
+            // Making sure that a query of both facet "a" and facet "b" yields 5 results
+            DrillDownQuery q3 = new DrillDownQuery(config);
+            q3.Add("a");
+            q3.Add("b");
+            docs = searcher.Search(q3, 100);
+
+            Assert.AreEqual(5, docs.TotalHits);
+            // Check that content:foo (which yields 50% results) and facet/b (which yields 20%)
+            // would gather together 10 results (10%..) 
+            Query fooQuery = new TermQuery(new Term("content", "foo"));
+            DrillDownQuery q4 = new DrillDownQuery(config, fooQuery);
+            q4.Add("b");
+            docs = searcher.Search(q4, 100);
+            Assert.AreEqual(10, docs.TotalHits);
+        }
+
+        [Test]
+        public virtual void TestQueryImplicitDefaultParams()
+        {
+            IndexSearcher searcher = NewSearcher(reader);
+
+            // Create the base query to start with
+            DrillDownQuery q = new DrillDownQuery(config);
+            q.Add("a");
+
+            // Making sure the query yields 5 documents with the facet "b" and the
+            // previous (facet "a") query as a base query
+            DrillDownQuery q2 = new DrillDownQuery(config, q);
+            q2.Add("b");
+            TopDocs docs = searcher.Search(q2, 100);
+            Assert.AreEqual(5, docs.TotalHits);
+
+            // Check that content:foo (which yields 50% results) and facet/b (which yields 20%)
+            // would gather together 10 results (10%..) 
+            Query fooQuery = new TermQuery(new Term("content", "foo"));
+            DrillDownQuery q4 = new DrillDownQuery(config, fooQuery);
+            q4.Add("b");
+            docs = searcher.Search(q4, 100);
+            Assert.AreEqual(10, docs.TotalHits);
+        }
+
+        [Test]
+        public virtual void TestScoring()
+        {
+            // verify that drill-down queries do not modify scores
+            IndexSearcher searcher = NewSearcher(reader);
+
+            float[] scores = new float[reader.MaxDoc];
+
+            Query q = new TermQuery(new Term("content", "foo"));
+            TopDocs docs = searcher.Search(q, reader.MaxDoc); // fetch all available docs to this query
+            foreach (ScoreDoc sd in docs.ScoreDocs)
+            {
+                scores[sd.Doc] = sd.Score;
+            }
+
+            // create a drill-down query with category "a", scores should not change
+            DrillDownQuery q2 = new DrillDownQuery(config, q);
+            q2.Add("a");
+            docs = searcher.Search(q2, reader.MaxDoc); // fetch all available docs to this query
+            foreach (ScoreDoc sd in docs.ScoreDocs)
+            {
+                Assert.AreEqual(scores[sd.Doc], sd.Score, 0f, "score of doc=" + sd.Doc + " modified");
+            }
+        }
+
+        [Test]
+        public virtual void TestScoringNoBaseQuery()
+        {
+            // verify that drill-down queries (with no base query) returns 0.0 score
+            IndexSearcher searcher = NewSearcher(reader);
+
+            DrillDownQuery q = new DrillDownQuery(config);
+            q.Add("a");
+            TopDocs docs = searcher.Search(q, reader.MaxDoc); // fetch all available docs to this query
+            foreach (ScoreDoc sd in docs.ScoreDocs)
+            {
+                Assert.AreEqual(0f, sd.Score, 0f);
+            }
+        }
+
+        [Test]
+        public virtual void TestTermNonDefault()
+        {
+            string aField = config.GetDimConfig("a").indexFieldName;
+            Term termA = DrillDownQuery.Term(aField, "a");
+            Assert.AreEqual(new Term(aField, "a"), termA);
+
+            string bField = config.GetDimConfig("b").indexFieldName;
+            Term termB = DrillDownQuery.Term(bField, "b");
+            Assert.AreEqual(new Term(bField, "b"), termB);
+        }
+
+        [Test]
+        public virtual void TestClone()
+        {
+            var q = new DrillDownQuery(config, new MatchAllDocsQuery());
+            q.Add("a");
+
+            var clone = q.Clone() as DrillDownQuery;
+            Assert.NotNull(clone);
+            clone.Add("b");
+            Assert.False(q.ToString().Equals(clone.ToString()), "query wasn't cloned: source=" + q + " clone=" + clone);
+        }
+
+        [Test]
+        public virtual void TestNoDrillDown()
+        {
+            Query @base = new MatchAllDocsQuery();
+            DrillDownQuery q = new DrillDownQuery(config, @base);
+            Query rewrite = q.Rewrite(reader).Rewrite(reader);
+            Assert.AreSame(@base, rewrite);
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/TestDrillSideways.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/TestDrillSideways.cs b/src/Lucene.Net.Tests/core/Facet/TestDrillSideways.cs
new file mode 100644
index 0000000..14b3e12
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/TestDrillSideways.cs
@@ -0,0 +1,1332 @@
+using System;
+using System.Diagnostics;
+using System.Collections.Generic;
+using System.Linq;
+using Lucene.Net.Randomized.Generators;
+using Lucene.Net.Support;
+
+namespace Lucene.Net.Facet
+{
+
+    /*
+     * 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 MockAnalyzer = Lucene.Net.Analysis.MockAnalyzer;
+    using Document = Lucene.Net.Documents.Document;
+    using Field = Lucene.Net.Documents.Field;
+    using StringField = Lucene.Net.Documents.StringField;
+    using DrillSidewaysResult = Lucene.Net.Facet.DrillSideways.DrillSidewaysResult;
+    using DefaultSortedSetDocValuesReaderState = Lucene.Net.Facet.SortedSet.DefaultSortedSetDocValuesReaderState;
+    using SortedSetDocValuesFacetField = Lucene.Net.Facet.SortedSet.SortedSetDocValuesFacetField;
+    using SortedSetDocValuesReaderState = Lucene.Net.Facet.SortedSet.SortedSetDocValuesReaderState;
+    using TaxonomyReader = Lucene.Net.Facet.Taxonomy.TaxonomyReader;
+    using DirectoryTaxonomyReader = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyReader;
+    using DirectoryTaxonomyWriter = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter;
+    using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext;
+    using IndexReader = Lucene.Net.Index.IndexReader;
+    using IndexWriterConfig = Lucene.Net.Index.IndexWriterConfig;
+    using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter;
+    using Term = Lucene.Net.Index.Term;
+    using Collector = Lucene.Net.Search.Collector;
+    using DocIdSet = Lucene.Net.Search.DocIdSet;
+    using Filter = Lucene.Net.Search.Filter;
+    using IndexSearcher = Lucene.Net.Search.IndexSearcher;
+    using MatchAllDocsQuery = Lucene.Net.Search.MatchAllDocsQuery;
+    using Query = Lucene.Net.Search.Query;
+    using ScoreDoc = Lucene.Net.Search.ScoreDoc;
+    using Scorer = Lucene.Net.Search.Scorer;
+    using Sort = Lucene.Net.Search.Sort;
+    using SortField = Lucene.Net.Search.SortField;
+    using TermQuery = Lucene.Net.Search.TermQuery;
+    using TopDocs = Lucene.Net.Search.TopDocs;
+    using Directory = Lucene.Net.Store.Directory;
+    using Bits = Lucene.Net.Util.Bits;
+    using BytesRef = Lucene.Net.Util.BytesRef;
+    using FixedBitSet = Lucene.Net.Util.FixedBitSet;
+    using IOUtils = Lucene.Net.Util.IOUtils;
+    using InPlaceMergeSorter = Lucene.Net.Util.InPlaceMergeSorter;
+    using InfoStream = Lucene.Net.Util.InfoStream;
+    using TestUtil = Lucene.Net.Util.TestUtil;
+    using NUnit.Framework;
+
+    [TestFixture]
+    public class TestDrillSideways : FacetTestCase
+    {
+
+        [Test]
+        public virtual void TestBasic()
+        {
+            Directory dir = NewDirectory();
+            Directory taxoDir = NewDirectory();
+
+            // Writes facet ords to a separate directory from the
+            // main index:
+            var taxoWriter = new DirectoryTaxonomyWriter(taxoDir, IndexWriterConfig.OpenMode_e.CREATE);
+
+            FacetsConfig config = new FacetsConfig();
+            config.SetHierarchical("Publish Date", true);
+
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir);
+
+            Document doc = new Document();
+            doc.Add(new FacetField("Author", "Bob"));
+            doc.Add(new FacetField("Publish Date", "2010", "10", "15"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            doc = new Document();
+            doc.Add(new FacetField("Author", "Lisa"));
+            doc.Add(new FacetField("Publish Date", "2010", "10", "20"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            doc = new Document();
+            doc.Add(new FacetField("Author", "Lisa"));
+            doc.Add(new FacetField("Publish Date", "2012", "1", "1"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            doc = new Document();
+            doc.Add(new FacetField("Author", "Susan"));
+            doc.Add(new FacetField("Publish Date", "2012", "1", "7"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            doc = new Document();
+            doc.Add(new FacetField("Author", "Frank"));
+            doc.Add(new FacetField("Publish Date", "1999", "5", "5"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            // NRT open
+            IndexSearcher searcher = NewSearcher(writer.Reader);
+
+            //System.out.println("searcher=" + searcher);
+
+            // NRT open
+            var taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+
+            DrillSideways ds = new DrillSideways(searcher, config, taxoReader);
+
+            //  case: drill-down on a single field; in this
+            // case the drill-sideways + drill-down counts ==
+            // drill-down of just the query: 
+            DrillDownQuery ddq = new DrillDownQuery(config);
+            ddq.Add("Author", "Lisa");
+            DrillSidewaysResult r = ds.Search(null, ddq, 10);
+            Assert.AreEqual(2, r.Hits.TotalHits);
+            // Publish Date is only drill-down, and Lisa published
+            // one in 2012 and one in 2010:
+            Assert.AreEqual("dim=Publish Date path=[] value=2 childCount=2\n  2010 (1)\n  2012 (1)\n", r.Facets.GetTopChildren(10, "Publish Date").ToString());
+
+            // Author is drill-sideways + drill-down: Lisa
+            // (drill-down) published twice, and Frank/Susan/Bob
+            // published once:
+            Assert.AreEqual("dim=Author path=[] value=5 childCount=4\n  Lisa (2)\n  Bob (1)\n  Susan (1)\n  Frank (1)\n", r.Facets.GetTopChildren(10, "Author").ToString());
+
+            // Same simple case, but no baseQuery (pure browse):
+            // drill-down on a single field; in this case the
+            // drill-sideways + drill-down counts == drill-down of
+            // just the query:
+            ddq = new DrillDownQuery(config);
+            ddq.Add("Author", "Lisa");
+            r = ds.Search(null, ddq, 10);
+
+            Assert.AreEqual(2, r.Hits.TotalHits);
+            // Publish Date is only drill-down, and Lisa published
+            // one in 2012 and one in 2010:
+            Assert.AreEqual("dim=Publish Date path=[] value=2 childCount=2\n  2010 (1)\n  2012 (1)\n", r.Facets.GetTopChildren(10, "Publish Date").ToString());
+
+            // Author is drill-sideways + drill-down: Lisa
+            // (drill-down) published twice, and Frank/Susan/Bob
+            // published once:
+            Assert.AreEqual("dim=Author path=[] value=5 childCount=4\n  Lisa (2)\n  Bob (1)\n  Susan (1)\n  Frank (1)\n", r.Facets.GetTopChildren(10, "Author").ToString());
+
+            // Another simple case: drill-down on single fields
+            // but OR of two values
+            ddq = new DrillDownQuery(config);
+            ddq.Add("Author", "Lisa");
+            ddq.Add("Author", "Bob");
+            r = ds.Search(null, ddq, 10);
+            Assert.AreEqual(3, r.Hits.TotalHits);
+            // Publish Date is only drill-down: Lisa and Bob
+            // (drill-down) published twice in 2010 and once in 2012:
+            Assert.AreEqual("dim=Publish Date path=[] value=3 childCount=2\n  2010 (2)\n  2012 (1)\n", r.Facets.GetTopChildren(10, "Publish Date").ToString());
+            // Author is drill-sideways + drill-down: Lisa
+            // (drill-down) published twice, and Frank/Susan/Bob
+            // published once:
+            Assert.AreEqual("dim=Author path=[] value=5 childCount=4\n  Lisa (2)\n  Bob (1)\n  Susan (1)\n  Frank (1)\n", r.Facets.GetTopChildren(10, "Author").ToString());
+
+            Assert.True(r.Facets is MultiFacets);
+            IList<FacetResult> allResults = r.Facets.GetAllDims(10);
+            Assert.AreEqual(2, allResults.Count);
+            Assert.AreEqual("dim=Author path=[] value=5 childCount=4\n  Lisa (2)\n  Bob (1)\n  Susan (1)\n  Frank (1)\n", allResults[0].ToString());
+            Assert.AreEqual("dim=Publish Date path=[] value=3 childCount=2\n  2010 (2)\n  2012 (1)\n", allResults[1].ToString());
+
+            // More interesting case: drill-down on two fields
+            ddq = new DrillDownQuery(config);
+            ddq.Add("Author", "Lisa");
+            ddq.Add("Publish Date", "2010");
+            r = ds.Search(null, ddq, 10);
+            Assert.AreEqual(1, r.Hits.TotalHits);
+            // Publish Date is drill-sideways + drill-down: Lisa
+            // (drill-down) published once in 2010 and once in 2012:
+            Assert.AreEqual("dim=Publish Date path=[] value=2 childCount=2\n  2010 (1)\n  2012 (1)\n", r.Facets.GetTopChildren(10, "Publish Date").ToString());
+            // Author is drill-sideways + drill-down:
+            // only Lisa & Bob published (once each) in 2010:
+            Assert.AreEqual("dim=Author path=[] value=2 childCount=2\n  Bob (1)\n  Lisa (1)\n", r.Facets.GetTopChildren(10, "Author").ToString());
+
+            // Even more interesting case: drill down on two fields,
+            // but one of them is OR
+            ddq = new DrillDownQuery(config);
+
+            // Drill down on Lisa or Bob:
+            ddq.Add("Author", "Lisa");
+            ddq.Add("Publish Date", "2010");
+            ddq.Add("Author", "Bob");
+            r = ds.Search(null, ddq, 10);
+            Assert.AreEqual(2, r.Hits.TotalHits);
+            // Publish Date is both drill-sideways + drill-down:
+            // Lisa or Bob published twice in 2010 and once in 2012:
+            Assert.AreEqual("dim=Publish Date path=[] value=3 childCount=2\n  2010 (2)\n  2012 (1)\n", r.Facets.GetTopChildren(10, "Publish Date").ToString());
+            // Author is drill-sideways + drill-down:
+            // only Lisa & Bob published (once each) in 2010:
+            Assert.AreEqual("dim=Author path=[] value=2 childCount=2\n  Bob (1)\n  Lisa (1)\n", r.Facets.GetTopChildren(10, "Author").ToString());
+
+            // Test drilling down on invalid field:
+            ddq = new DrillDownQuery(config);
+            ddq.Add("Foobar", "Baz");
+            r = ds.Search(null, ddq, 10);
+            Assert.AreEqual(0, r.Hits.TotalHits);
+            Assert.Null(r.Facets.GetTopChildren(10, "Publish Date"));
+            Assert.Null(r.Facets.GetTopChildren(10, "Foobar"));
+
+            // Test drilling down on valid term or'd with invalid term:
+            ddq = new DrillDownQuery(config);
+            ddq.Add("Author", "Lisa");
+            ddq.Add("Author", "Tom");
+            r = ds.Search(null, ddq, 10);
+            Assert.AreEqual(2, r.Hits.TotalHits);
+            // Publish Date is only drill-down, and Lisa published
+            // one in 2012 and one in 2010:
+            Assert.AreEqual("dim=Publish Date path=[] value=2 childCount=2\n  2010 (1)\n  2012 (1)\n", r.Facets.GetTopChildren(10, "Publish Date").ToString());
+            // Author is drill-sideways + drill-down: Lisa
+            // (drill-down) published twice, and Frank/Susan/Bob
+            // published once:
+            Assert.AreEqual("dim=Author path=[] value=5 childCount=4\n  Lisa (2)\n  Bob (1)\n  Susan (1)\n  Frank (1)\n", r.Facets.GetTopChildren(10, "Author").ToString());
+
+            // LUCENE-4915: test drilling down on a dimension but
+            // NOT facet counting it:
+            ddq = new DrillDownQuery(config);
+            ddq.Add("Author", "Lisa");
+            ddq.Add("Author", "Tom");
+            r = ds.Search(null, ddq, 10);
+            Assert.AreEqual(2, r.Hits.TotalHits);
+            // Publish Date is only drill-down, and Lisa published
+            // one in 2012 and one in 2010:
+            Assert.AreEqual("dim=Publish Date path=[] value=2 childCount=2\n  2010 (1)\n  2012 (1)\n", r.Facets.GetTopChildren(10, "Publish Date").ToString());
+
+            // Test main query gets null scorer:
+            ddq = new DrillDownQuery(config, new TermQuery(new Term("foobar", "baz")));
+            ddq.Add("Author", "Lisa");
+            r = ds.Search(null, ddq, 10);
+
+            Assert.AreEqual(0, r.Hits.TotalHits);
+            Assert.Null(r.Facets.GetTopChildren(10, "Publish Date"));
+            Assert.Null(r.Facets.GetTopChildren(10, "Author"));
+            IOUtils.Close(searcher.IndexReader, taxoReader, writer, taxoWriter, dir, taxoDir);
+        }
+
+        [Test]
+        public virtual void TestSometimesInvalidDrillDown()
+        {
+            Directory dir = NewDirectory();
+            Directory taxoDir = NewDirectory();
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir);
+
+            // Writes facet ords to a separate directory from the
+            // main index:
+            DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir, IndexWriterConfig.OpenMode_e.CREATE);
+
+            FacetsConfig config = new FacetsConfig();
+            config.SetHierarchical("Publish Date", true);
+
+            Document doc = new Document();
+            doc.Add(new FacetField("Author", "Bob"));
+            doc.Add(new FacetField("Publish Date", "2010", "10", "15"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            doc = new Document();
+            doc.Add(new FacetField("Author", "Lisa"));
+            doc.Add(new FacetField("Publish Date", "2010", "10", "20"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            writer.Commit();
+
+            // 2nd segment has no Author:
+            doc = new Document();
+            doc.Add(new FacetField("Foobar", "Lisa"));
+            doc.Add(new FacetField("Publish Date", "2012", "1", "1"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            // NRT open
+            IndexSearcher searcher = NewSearcher(writer.Reader);
+
+            //System.out.println("searcher=" + searcher);
+
+            // NRT open
+            var taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+
+            DrillDownQuery ddq = new DrillDownQuery(config);
+            ddq.Add("Author", "Lisa");
+            DrillSidewaysResult r = (new DrillSideways(searcher, config, taxoReader)).Search(null, ddq, 10);
+
+            Assert.AreEqual(1, r.Hits.TotalHits);
+            // Publish Date is only drill-down, and Lisa published
+            // one in 2012 and one in 2010:
+            Assert.AreEqual("dim=Publish Date path=[] value=1 childCount=1\n  2010 (1)\n", r.Facets.GetTopChildren(10, "Publish Date").ToString());
+            // Author is drill-sideways + drill-down: Lisa
+            // (drill-down) published once, and Bob
+            // published once:
+            Assert.AreEqual("dim=Author path=[] value=2 childCount=2\n  Bob (1)\n  Lisa (1)\n", r.Facets.GetTopChildren(10, "Author").ToString());
+
+            IOUtils.Close(searcher.IndexReader, taxoReader, writer, taxoWriter, dir, taxoDir);
+        }
+
+        [Test]
+        public virtual void TestMultipleRequestsPerDim()
+        {
+            Directory dir = NewDirectory();
+            Directory taxoDir = NewDirectory();
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir);
+
+            // Writes facet ords to a separate directory from the
+            // main index:
+            DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir, IndexWriterConfig.OpenMode_e.CREATE);
+
+            FacetsConfig config = new FacetsConfig();
+            config.SetHierarchical("dim", true);
+
+            Document doc = new Document();
+            doc.Add(new FacetField("dim", "a", "x"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            doc = new Document();
+            doc.Add(new FacetField("dim", "a", "y"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            doc = new Document();
+            doc.Add(new FacetField("dim", "a", "z"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            doc = new Document();
+            doc.Add(new FacetField("dim", "b"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            doc = new Document();
+            doc.Add(new FacetField("dim", "c"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            doc = new Document();
+            doc.Add(new FacetField("dim", "d"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            // NRT open
+            IndexSearcher searcher = NewSearcher(writer.Reader);
+
+            //System.out.println("searcher=" + searcher);
+
+            // NRT open
+            var taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+
+            DrillDownQuery ddq = new DrillDownQuery(config);
+            ddq.Add("dim", "a");
+            DrillSidewaysResult r = (new DrillSideways(searcher, config, taxoReader)).Search(null, ddq, 10);
+
+            Assert.AreEqual(3, r.Hits.TotalHits);
+            Assert.AreEqual("dim=dim path=[] value=6 childCount=4\n  a (3)\n  b (1)\n  c (1)\n  d (1)\n", r.Facets.GetTopChildren(10, "dim").ToString());
+            Assert.AreEqual("dim=dim path=[a] value=3 childCount=3\n  x (1)\n  y (1)\n  z (1)\n", r.Facets.GetTopChildren(10, "dim", "a").ToString());
+
+            IOUtils.Close(searcher.IndexReader, taxoReader, writer, taxoWriter, dir, taxoDir);
+        }
+
+        internal class Doc : IComparable<Doc>
+        {
+            internal string id;
+            internal string contentToken;
+
+            public Doc()
+            {
+            }
+
+            // -1 if the doc is missing this dim, else the index
+            // -into the values for this dim:
+            internal int[] dims;
+
+            // 2nd value per dim for the doc (so we test
+            // multi-valued fields):
+            internal int[] dims2;
+            internal bool deleted;
+
+            public virtual int CompareTo(Doc other)
+            {
+                return id.CompareTo(other.id);
+            }
+        }
+
+        private double aChance, bChance, cChance;
+
+        private string randomContentToken(bool isQuery)
+        {
+            double d = Random().NextDouble();
+            if (isQuery)
+            {
+                if (d < 0.33)
+                {
+                    return "a";
+                }
+                else if (d < 0.66)
+                {
+                    return "b";
+                }
+                else
+                {
+                    return "c";
+                }
+            }
+            else
+            {
+                if (d <= aChance)
+                {
+                    return "a";
+                }
+                else if (d < aChance + bChance)
+                {
+                    return "b";
+                }
+                else
+                {
+                    return "c";
+                }
+            }
+        }
+
+        [Test]
+        public virtual void TestRandom()
+        {
+
+            bool canUseDV = DefaultCodecSupportsSortedSet();
+
+            while (aChance == 0.0)
+            {
+                aChance = Random().NextDouble();
+            }
+            while (bChance == 0.0)
+            {
+                bChance = Random().NextDouble();
+            }
+            while (cChance == 0.0)
+            {
+                cChance = Random().NextDouble();
+            }
+            //aChance = .01;
+            //bChance = 0.5;
+            //cChance = 1.0;
+            double sum = aChance + bChance + cChance;
+            aChance /= sum;
+            bChance /= sum;
+            cChance /= sum;
+
+            int numDims = TestUtil.NextInt(Random(), 2, 5);
+            //int numDims = 3;
+            int numDocs = AtLeast(3000);
+            //int numDocs = 20;
+            if (VERBOSE)
+            {
+                Console.WriteLine("numDims=" + numDims + " numDocs=" + numDocs + " aChance=" + aChance + " bChance=" + bChance + " cChance=" + cChance);
+            }
+            string[][] dimValues = new string[numDims][];
+            int valueCount = 2;
+
+            for (int dim = 0; dim < numDims; dim++)
+            {
+                var values = new HashSet<string>();
+                while (values.Count < valueCount)
+                {
+                    var str = TestUtil.RandomRealisticUnicodeString(Random());
+                    //String s = TestUtil.randomString(Random());
+                    if (str.Length > 0)
+                    {
+                        values.Add(str);
+                    }
+                }
+                dimValues[dim] = values.ToArray();
+                valueCount *= 2;
+            }
+
+            IList<Doc> docs = new List<Doc>();
+            for (int i = 0; i < numDocs; i++)
+            {
+                Doc doc = new Doc();
+                doc.id = "" + i;
+                doc.contentToken = randomContentToken(false);
+                doc.dims = new int[numDims];
+                doc.dims2 = new int[numDims];
+                for (int dim = 0; dim < numDims; dim++)
+                {
+                    if (Random().Next(5) == 3)
+                    {
+                        // This doc is missing this dim:
+                        doc.dims[dim] = -1;
+                    }
+                    else if (dimValues[dim].Length <= 4)
+                    {
+                        int dimUpto = 0;
+                        doc.dims[dim] = dimValues[dim].Length - 1;
+                        while (dimUpto < dimValues[dim].Length)
+                        {
+                            if (Random().NextBoolean())
+                            {
+                                doc.dims[dim] = dimUpto;
+                                break;
+                            }
+                            dimUpto++;
+                        }
+                    }
+                    else
+                    {
+                        doc.dims[dim] = Random().Next(dimValues[dim].Length);
+                    }
+
+                    if (Random().Next(5) == 3)
+                    {
+                        // 2nd value:
+                        doc.dims2[dim] = Random().Next(dimValues[dim].Length);
+                    }
+                    else
+                    {
+                        doc.dims2[dim] = -1;
+                    }
+                }
+                docs.Add(doc);
+            }
+
+            Directory d = NewDirectory();
+            Directory td = NewDirectory();
+
+            IndexWriterConfig iwc = NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random()));
+            iwc.SetInfoStream(InfoStream.NO_OUTPUT);
+            var w = new RandomIndexWriter(Random(), d, iwc);
+            var tw = new DirectoryTaxonomyWriter(td, IndexWriterConfig.OpenMode_e.CREATE);
+            FacetsConfig config = new FacetsConfig();
+            for (int i = 0; i < numDims; i++)
+            {
+                config.SetMultiValued("dim" + i, true);
+            }
+
+            bool doUseDV = canUseDV && Random().NextBoolean();
+
+            foreach (Doc rawDoc in docs)
+            {
+                Document doc = new Document();
+                doc.Add(NewStringField("id", rawDoc.id, Field.Store.YES));
+                doc.Add(NewStringField("content", rawDoc.contentToken, Field.Store.NO));
+
+                if (VERBOSE)
+                {
+                    Console.WriteLine("  doc id=" + rawDoc.id + " token=" + rawDoc.contentToken);
+                }
+                for (int dim = 0; dim < numDims; dim++)
+                {
+                    int dimValue = rawDoc.dims[dim];
+                    if (dimValue != -1)
+                    {
+                        if (doUseDV)
+                        {
+                            doc.Add(new SortedSetDocValuesFacetField("dim" + dim, dimValues[dim][dimValue]));
+                        }
+                        else
+                        {
+                            doc.Add(new FacetField("dim" + dim, dimValues[dim][dimValue]));
+                        }
+                        doc.Add(new StringField("dim" + dim, dimValues[dim][dimValue], Field.Store.YES));
+                        if (VERBOSE)
+                        {
+                            Console.WriteLine("    dim" + dim + "=" + new BytesRef(dimValues[dim][dimValue]));
+                        }
+                    }
+                    int dimValue2 = rawDoc.dims2[dim];
+                    if (dimValue2 != -1)
+                    {
+                        if (doUseDV)
+                        {
+                            doc.Add(new SortedSetDocValuesFacetField("dim" + dim, dimValues[dim][dimValue2]));
+                        }
+                        else
+                        {
+                            doc.Add(new FacetField("dim" + dim, dimValues[dim][dimValue2]));
+                        }
+                        doc.Add(new StringField("dim" + dim, dimValues[dim][dimValue2], Field.Store.YES));
+                        if (VERBOSE)
+                        {
+                            Console.WriteLine("      dim" + dim + "=" + new BytesRef(dimValues[dim][dimValue2]));
+                        }
+                    }
+                }
+
+                w.AddDocument(config.Build(tw, doc));
+            }
+
+            if (Random().NextBoolean())
+            {
+                // Randomly delete a few docs:
+                int numDel = TestUtil.NextInt(Random(), 1, (int)(numDocs * 0.05));
+                if (VERBOSE)
+                {
+                    Console.WriteLine("delete " + numDel);
+                }
+                int delCount = 0;
+                while (delCount < numDel)
+                {
+                    Doc doc = docs[Random().Next(docs.Count)];
+                    if (!doc.deleted)
+                    {
+                        if (VERBOSE)
+                        {
+                            Console.WriteLine("  delete id=" + doc.id);
+                        }
+                        doc.deleted = true;
+                        w.DeleteDocuments(new Term("id", doc.id));
+                        delCount++;
+                    }
+                }
+            }
+
+            if (Random().NextBoolean())
+            {
+                if (VERBOSE)
+                {
+                    Console.WriteLine("TEST: forceMerge(1)...");
+                }
+                w.ForceMerge(1);
+            }
+            IndexReader r = w.Reader;
+
+            SortedSetDocValuesReaderState sortedSetDVState;
+            IndexSearcher s = NewSearcher(r);
+
+            if (doUseDV)
+            {
+                sortedSetDVState = new DefaultSortedSetDocValuesReaderState(s.IndexReader);
+            }
+            else
+            {
+                sortedSetDVState = null;
+            }
+
+            if (VERBOSE)
+            {
+                Console.WriteLine("r.numDocs() = " + r.NumDocs);
+            }
+
+            // NRT open
+            var tr = new DirectoryTaxonomyReader(tw);
+
+            int numIters = AtLeast(10);
+
+            for (int iter = 0; iter < numIters; iter++)
+            {
+
+                string contentToken = Random().Next(30) == 17 ? null : randomContentToken(true);
+                int numDrillDown = TestUtil.NextInt(Random(), 1, Math.Min(4, numDims));
+                if (VERBOSE)
+                {
+                    Console.WriteLine("\nTEST: iter=" + iter + " baseQuery=" + contentToken + " numDrillDown=" + numDrillDown + " useSortedSetDV=" + doUseDV);
+                }
+
+                string[][] drillDowns = new string[numDims][];
+
+                int count = 0;
+                bool anyMultiValuedDrillDowns = false;
+                while (count < numDrillDown)
+                {
+                    int dim = Random().Next(numDims);
+                    if (drillDowns[dim] == null)
+                    {
+                        if (Random().NextBoolean())
+                        {
+                            // Drill down on one value:
+                            drillDowns[dim] = new string[] { dimValues[dim][Random().Next(dimValues[dim].Length)] };
+                        }
+                        else
+                        {
+                            int orCount = TestUtil.NextInt(Random(), 1, Math.Min(5, dimValues[dim].Length));
+                            drillDowns[dim] = new string[orCount];
+                            anyMultiValuedDrillDowns |= orCount > 1;
+                            for (int i = 0; i < orCount; i++)
+                            {
+                                while (true)
+                                {
+                                    string value = dimValues[dim][Random().Next(dimValues[dim].Length)];
+                                    for (int j = 0; j < i; j++)
+                                    {
+                                        if (value.Equals(drillDowns[dim][j]))
+                                        {
+                                            value = null;
+                                            break;
+                                        }
+                                    }
+                                    if (value != null)
+                                    {
+                                        drillDowns[dim][i] = value;
+                                        break;
+                                    }
+                                }
+                            }
+                        }
+                        if (VERBOSE)
+                        {
+                            BytesRef[] values = new BytesRef[drillDowns[dim].Length];
+                            for (int i = 0; i < values.Length; i++)
+                            {
+                                values[i] = new BytesRef(drillDowns[dim][i]);
+                            }
+                            Console.WriteLine("  dim" + dim + "=" + Arrays.ToString(values));
+                        }
+                        count++;
+                    }
+                }
+
+                Query baseQuery;
+                if (contentToken == null)
+                {
+                    baseQuery = new MatchAllDocsQuery();
+                }
+                else
+                {
+                    baseQuery = new TermQuery(new Term("content", contentToken));
+                }
+
+                DrillDownQuery ddq = new DrillDownQuery(config, baseQuery);
+
+                for (int dim = 0; dim < numDims; dim++)
+                {
+                    if (drillDowns[dim] != null)
+                    {
+                        foreach (string value in drillDowns[dim])
+                        {
+                            ddq.Add("dim" + dim, value);
+                        }
+                    }
+                }
+
+                Filter filter;
+                if (Random().Next(7) == 6)
+                {
+                    if (VERBOSE)
+                    {
+                        Console.WriteLine("  only-even filter");
+                    }
+                    filter = new FilterAnonymousInnerClassHelper(this);
+                }
+                else
+                {
+                    filter = null;
+                }
+
+                // Verify docs are always collected in order.  If we
+                // had an AssertingScorer it could catch it when
+                // Weight.scoresDocsOutOfOrder lies!:
+                (new DrillSideways(s, config, tr)).Search(ddq, new CollectorAnonymousInnerClassHelper(this, s));
+
+                // Also separately verify that DS respects the
+                // scoreSubDocsAtOnce method, to ensure that all
+                // subScorers are on the same docID:
+                if (!anyMultiValuedDrillDowns)
+                {
+                    // Can only do this test when there are no OR'd
+                    // drill-down values, because in that case it's
+                    // easily possible for one of the DD terms to be on
+                    // a future docID:
+                    new DrillSidewaysAnonymousInnerClassHelper(this, s, config, tr)
+                    .Search(ddq, new AssertingSubDocsAtOnceCollector());
+                }
+
+                TestFacetResult expected = slowDrillSidewaysSearch(s, docs, contentToken, drillDowns, dimValues, filter);
+
+                Sort sort = new Sort(new SortField("id", SortField.Type_e.STRING));
+                DrillSideways ds;
+                if (doUseDV)
+                {
+                    ds = new DrillSideways(s, config, sortedSetDVState);
+                }
+                else
+                {
+                    ds = new DrillSidewaysAnonymousInnerClassHelper2(this, s, config, tr, drillDowns);
+                }
+
+                // Retrieve all facets:
+                DrillSidewaysResult actual = ds.Search(ddq, filter, null, numDocs, sort, true, true);
+
+                TopDocs hits = s.Search(baseQuery, numDocs);
+                IDictionary<string, float?> scores = new Dictionary<string, float?>();
+                foreach (ScoreDoc sd in hits.ScoreDocs)
+                {
+                    scores[s.Doc(sd.Doc).Get("id")] = sd.Score;
+                }
+                if (VERBOSE)
+                {
+                    Console.WriteLine("  verify all facets");
+                }
+                VerifyEquals(dimValues, s, expected, actual, scores, doUseDV);
+
+                // Make sure drill down doesn't change score:
+                TopDocs ddqHits = s.Search(ddq, filter, numDocs);
+                Assert.AreEqual(expected.Hits.Count, ddqHits.TotalHits);
+                for (int i = 0; i < expected.Hits.Count; i++)
+                {
+                    // Score should be IDENTICAL:
+                    Assert.AreEqual(scores[expected.Hits[i].id], ddqHits.ScoreDocs[i].Score);
+                }
+            }
+
+            IOUtils.Close(r, tr, w, tw, d, td);
+        }
+
+        private class FilterAnonymousInnerClassHelper : Filter
+        {
+            private readonly TestDrillSideways outerInstance;
+
+            public FilterAnonymousInnerClassHelper(TestDrillSideways outerInstance)
+            {
+                this.outerInstance = outerInstance;
+            }
+
+            public override DocIdSet GetDocIdSet(AtomicReaderContext context, Bits acceptDocs)
+            {
+                int maxDoc = context.Reader.MaxDoc;
+                FixedBitSet bits = new FixedBitSet(maxDoc);
+                for (int docID = 0; docID < maxDoc; docID++)
+                {
+                    // Keeps only the even ids:
+                    if ((acceptDocs == null || acceptDocs.Get(docID)) && (Convert.ToInt32(context.Reader.Document(docID).Get("id")) & 1) == 0)
+                    {
+                        bits.Set(docID);
+                    }
+                }
+                return bits;
+            }
+        }
+
+        private class CollectorAnonymousInnerClassHelper : Collector
+        {
+            private readonly TestDrillSideways outerInstance;
+
+            private IndexSearcher s;
+
+            public CollectorAnonymousInnerClassHelper(TestDrillSideways outerInstance, IndexSearcher s)
+            {
+                this.outerInstance = outerInstance;
+                this.s = s;
+            }
+
+            internal int lastDocID;
+
+            public override Scorer Scorer
+            {
+                set
+                {
+                }
+            }
+
+            public override void Collect(int doc)
+            {
+                Debug.Assert(doc > lastDocID);
+                lastDocID = doc;
+            }
+
+            public override AtomicReaderContext NextReader
+            {
+                set
+                {
+                    lastDocID = -1;
+                }
+            }
+
+            public override bool AcceptsDocsOutOfOrder()
+            {
+                return false;
+            }
+        }
+
+        private class DrillSidewaysAnonymousInnerClassHelper : DrillSideways
+        {
+            private readonly TestDrillSideways outerInstance;
+
+            public DrillSidewaysAnonymousInnerClassHelper(TestDrillSideways outerInstance, IndexSearcher s, Lucene.Net.Facet.FacetsConfig config, TaxonomyReader tr)
+                : base(s, config, tr)
+            {
+                this.outerInstance = outerInstance;
+            }
+
+            protected override bool ScoreSubDocsAtOnce()
+            {
+                return true;
+            }
+        }
+
+        private class DrillSidewaysAnonymousInnerClassHelper2 : DrillSideways
+        {
+            private readonly TestDrillSideways outerInstance;
+
+            private new Lucene.Net.Facet.FacetsConfig config;
+            private string[][] drillDowns;
+
+            public DrillSidewaysAnonymousInnerClassHelper2(TestDrillSideways outerInstance, IndexSearcher s, Lucene.Net.Facet.FacetsConfig config, TaxonomyReader tr, string[][] drillDowns)
+                : base(s, config, tr)
+            {
+                this.outerInstance = outerInstance;
+                this.config = config;
+                this.drillDowns = drillDowns;
+            }
+
+            protected override Facets BuildFacetsResult(FacetsCollector drillDowns, FacetsCollector[] drillSideways, string[] drillSidewaysDims)
+            {
+                IDictionary<string, Facets> drillSidewaysFacets = new Dictionary<string, Facets>();
+                Facets drillDownFacets = outerInstance.GetTaxonomyFacetCounts(taxoReader, config, drillDowns);
+                if (drillSideways != null)
+                {
+                    for (int i = 0; i < drillSideways.Length; i++)
+                    {
+                        drillSidewaysFacets[drillSidewaysDims[i]] = outerInstance.GetTaxonomyFacetCounts(taxoReader, config, drillSideways[i]);
+                    }
+                }
+
+                if (drillSidewaysFacets.Count == 0)
+                {
+                    return drillDownFacets;
+                }
+                else
+                {
+                    return new MultiFacets(drillSidewaysFacets, drillDownFacets);
+                }
+
+            }
+        }
+
+        private class Counters
+        {
+            internal int[][] counts;
+
+            public Counters(string[][] dimValues)
+            {
+                counts = new int[dimValues.Length][];
+                for (int dim = 0; dim < dimValues.Length; dim++)
+                {
+                    counts[dim] = new int[dimValues[dim].Length];
+                }
+            }
+
+            public virtual void Inc(int[] dims, int[] dims2)
+            {
+                Inc(dims, dims2, -1);
+            }
+
+            public virtual void Inc(int[] dims, int[] dims2, int onlyDim)
+            {
+                Debug.Assert(dims.Length == counts.Length);
+                Debug.Assert(dims2.Length == counts.Length);
+                for (int dim = 0; dim < dims.Length; dim++)
+                {
+                    if (onlyDim == -1 || dim == onlyDim)
+                    {
+                        if (dims[dim] != -1)
+                        {
+                            counts[dim][dims[dim]]++;
+                        }
+                        if (dims2[dim] != -1 && dims2[dim] != dims[dim])
+                        {
+                            counts[dim][dims2[dim]]++;
+                        }
+                    }
+                }
+            }
+        }
+
+        internal class TestFacetResult
+        {
+            internal IList<Doc> Hits;
+            internal int[][] Counts;
+            internal int[] UniqueCounts;
+            public TestFacetResult()
+            {
+            }
+        }
+
+        private int[] GetTopNOrds(int[] counts, string[] values, int topN)
+        {
+            int[] ids = new int[counts.Length];
+            for (int i = 0; i < ids.Length; i++)
+            {
+                ids[i] = i;
+            }
+
+            // Naive (on purpose, to reduce bug in tester/gold):
+            // sort all ids, then return top N slice:
+            new InPlaceMergeSorterAnonymousInnerClassHelper(this, counts, values, ids).Sort(0, ids.Length);
+
+            if (topN > ids.Length)
+            {
+                topN = ids.Length;
+            }
+
+            int numSet = topN;
+            for (int i = 0; i < topN; i++)
+            {
+                if (counts[ids[i]] == 0)
+                {
+                    numSet = i;
+                    break;
+                }
+            }
+
+            int[] topNIDs = new int[numSet];
+            Array.Copy(ids, 0, topNIDs, 0, topNIDs.Length);
+            return topNIDs;
+        }
+
+        private class InPlaceMergeSorterAnonymousInnerClassHelper : InPlaceMergeSorter
+        {
+            private readonly TestDrillSideways outerInstance;
+
+            private int[] counts;
+            private string[] values;
+            private int[] ids;
+
+            public InPlaceMergeSorterAnonymousInnerClassHelper(TestDrillSideways outerInstance, int[] counts, string[] values, int[] ids)
+            {
+                this.outerInstance = outerInstance;
+                this.counts = counts;
+                this.values = values;
+                this.ids = ids;
+            }
+
+
+            protected override void Swap(int i, int j)
+            {
+                int id = ids[i];
+                ids[i] = ids[j];
+                ids[j] = id;
+            }
+
+            protected override int Compare(int i, int j)
+            {
+                int counti = counts[ids[i]];
+                int countj = counts[ids[j]];
+                // Sort by count descending...
+                if (counti > countj)
+                {
+                    return -1;
+                }
+                else if (counti < countj)
+                {
+                    return 1;
+                }
+                else
+                {
+                    // ... then by label ascending:
+                    return (new BytesRef(values[ids[i]])).CompareTo(new BytesRef(values[ids[j]]));
+                }
+            }
+
+        }
+
+        private TestFacetResult slowDrillSidewaysSearch(IndexSearcher s, IList<Doc> docs, string contentToken, string[][] drillDowns, string[][] dimValues, Filter onlyEven)
+        {
+            int numDims = dimValues.Length;
+
+            IList<Doc> hits = new List<Doc>();
+            Counters drillDownCounts = new Counters(dimValues);
+            Counters[] drillSidewaysCounts = new Counters[dimValues.Length];
+            for (int dim = 0; dim < numDims; dim++)
+            {
+                drillSidewaysCounts[dim] = new Counters(dimValues);
+            }
+
+            if (VERBOSE)
+            {
+                Console.WriteLine("  compute expected");
+            }
+
+            foreach (Doc doc in docs)
+            {
+                if (doc.deleted)
+                {
+                    continue;
+                }
+                if (onlyEven != null & (Convert.ToInt32(doc.id) & 1) != 0)
+                {
+                    continue;
+                }
+                if (contentToken == null || doc.contentToken.Equals(contentToken))
+                {
+                    int failDim = -1;
+                    for (int dim = 0; dim < numDims; dim++)
+                    {
+                        if (drillDowns[dim] != null)
+                        {
+                            string docValue = doc.dims[dim] == -1 ? null : dimValues[dim][doc.dims[dim]];
+                            string docValue2 = doc.dims2[dim] == -1 ? null : dimValues[dim][doc.dims2[dim]];
+                            bool matches = false;
+                            foreach (string value in drillDowns[dim])
+                            {
+                                if (value.Equals(docValue) || value.Equals(docValue2))
+                                {
+                                    matches = true;
+                                    break;
+                                }
+                            }
+                            if (!matches)
+                            {
+                                if (failDim == -1)
+                                {
+                                    // Doc could be a near-miss, if no other dim fails
+                                    failDim = dim;
+                                }
+                                else
+                                {
+                                    // Doc isn't a hit nor a near-miss
+                                    goto nextDocContinue;
+                                }
+                            }
+                        }
+                    }
+
+                    if (failDim == -1)
+                    {
+                        if (VERBOSE)
+                        {
+                            Console.WriteLine("    exp: id=" + doc.id + " is a hit");
+                        }
+                        // Hit:
+                        hits.Add(doc);
+                        drillDownCounts.Inc(doc.dims, doc.dims2);
+                        for (int dim = 0; dim < dimValues.Length; dim++)
+                        {
+                            drillSidewaysCounts[dim].Inc(doc.dims, doc.dims2);
+                        }
+                    }
+                    else
+                    {
+                        if (VERBOSE)
+                        {
+                            Console.WriteLine("    exp: id=" + doc.id + " is a near-miss on dim=" + failDim);
+                        }
+                        drillSidewaysCounts[failDim].Inc(doc.dims, doc.dims2, failDim);
+                    }
+                }
+            nextDocContinue: ;
+            }
+        nextDocBreak:
+
+            IDictionary<string, int?> idToDocID = new Dictionary<string, int?>();
+            for (int i = 0; i < s.IndexReader.MaxDoc; i++)
+            {
+                idToDocID[s.Doc(i).Get("id")] = i;
+            }
+
+            var hitsSorted = hits.ToArray();
+            Array.Sort(hitsSorted);
+            hits = hitsSorted.ToList();
+
+            TestFacetResult res = new TestFacetResult();
+            res.Hits = hits;
+            res.Counts = new int[numDims][];
+            res.UniqueCounts = new int[numDims];
+            for (int dim = 0; dim < numDims; dim++)
+            {
+                if (drillDowns[dim] != null)
+                {
+                    res.Counts[dim] = drillSidewaysCounts[dim].counts[dim];
+                }
+                else
+                {
+                    res.Counts[dim] = drillDownCounts.counts[dim];
+                }
+                int uniqueCount = 0;
+                for (int j = 0; j < res.Counts[dim].Length; j++)
+                {
+                    if (res.Counts[dim][j] != 0)
+                    {
+                        uniqueCount++;
+                    }
+                }
+                res.UniqueCounts[dim] = uniqueCount;
+            }
+
+            return res;
+        }
+
+        internal virtual void VerifyEquals(string[][] dimValues, IndexSearcher s, TestFacetResult expected, DrillSidewaysResult actual, IDictionary<string, float?> scores, bool isSortedSetDV)
+        {
+            if (VERBOSE)
+            {
+                Console.WriteLine("  verify totHits=" + expected.Hits.Count);
+            }
+            Assert.AreEqual(expected.Hits.Count, actual.Hits.TotalHits);
+            Assert.AreEqual(expected.Hits.Count, actual.Hits.ScoreDocs.Length);
+            for (int i = 0; i < expected.Hits.Count; i++)
+            {
+                if (VERBOSE)
+                {
+                    Console.WriteLine("    hit " + i + " expected=" + expected.Hits[i].id);
+                }
+                Assert.AreEqual(expected.Hits[i].id, s.Doc(actual.Hits.ScoreDocs[i].Doc).Get("id"));
+                // Score should be IDENTICAL:
+                Assert.AreEqual(scores[expected.Hits[i].id], actual.Hits.ScoreDocs[i].Score);
+            }
+
+            for (int dim = 0; dim < expected.Counts.Length; dim++)
+            {
+                int topN = Random().NextBoolean() ? dimValues[dim].Length : TestUtil.NextInt(Random(), 1, dimValues[dim].Length);
+                FacetResult fr = actual.Facets.GetTopChildren(topN, "dim" + dim);
+                if (VERBOSE)
+                {
+                    Console.WriteLine("    dim" + dim + " topN=" + topN + " (vs " + dimValues[dim].Length + " unique values)");
+                    Console.WriteLine("      actual");
+                }
+
+                int idx = 0;
+                IDictionary<string, int?> actualValues = new Dictionary<string, int?>();
+
+                if (fr != null)
+                {
+                    foreach (LabelAndValue labelValue in fr.labelValues)
+                    {
+                        actualValues[labelValue.label] = (int)labelValue.value;
+                        if (VERBOSE)
+                        {
+                            Console.WriteLine("        " + idx + ": " + new BytesRef(labelValue.label) + ": " + labelValue.value);
+                            idx++;
+                        }
+                    }
+                    Assert.AreEqual(expected.UniqueCounts[dim], fr.childCount, "dim=" + dim);
+                }
+
+                if (topN < dimValues[dim].Length)
+                {
+                    int[] topNIDs = GetTopNOrds(expected.Counts[dim], dimValues[dim], topN);
+                    if (VERBOSE)
+                    {
+                        idx = 0;
+                        Console.WriteLine("      expected (sorted)");
+                        for (int i = 0; i < topNIDs.Length; i++)
+                        {
+                            int expectedOrd = topNIDs[i];
+                            string value = dimValues[dim][expectedOrd];
+                            Console.WriteLine("        " + idx + ": " + new BytesRef(value) + ": " + expected.Counts[dim][expectedOrd]);
+                            idx++;
+                        }
+                    }
+                    if (VERBOSE)
+                    {
+                        Console.WriteLine("      topN=" + topN + " expectedTopN=" + topNIDs.Length);
+                    }
+
+                    if (fr != null)
+                    {
+                        Assert.AreEqual(topNIDs.Length, fr.labelValues.Length);
+                    }
+                    else
+                    {
+                        Assert.AreEqual(0, topNIDs.Length);
+                    }
+                    for (int i = 0; i < topNIDs.Length; i++)
+                    {
+                        int expectedOrd = topNIDs[i];
+                        Assert.AreEqual(expected.Counts[dim][expectedOrd], (int)fr.labelValues[i].value);
+                        if (isSortedSetDV)
+                        {
+                            // Tie-break facet labels are only in unicode
+                            // order with SortedSetDVFacets:
+                            Assert.AreEqual("value @ idx=" + i, dimValues[dim][expectedOrd], fr.labelValues[i].label);
+                        }
+                    }
+                }
+                else
+                {
+
+                    if (VERBOSE)
+                    {
+                        idx = 0;
+                        Console.WriteLine("      expected (unsorted)");
+                        for (int i = 0; i < dimValues[dim].Length; i++)
+                        {
+                            string value = dimValues[dim][i];
+                            if (expected.Counts[dim][i] != 0)
+                            {
+                                Console.WriteLine("        " + idx + ": " + new BytesRef(value) + ": " + expected.Counts[dim][i]);
+                                idx++;
+                            }
+                        }
+                    }
+
+                    int setCount = 0;
+                    for (int i = 0; i < dimValues[dim].Length; i++)
+                    {
+                        string value = dimValues[dim][i];
+                        if (expected.Counts[dim][i] != 0)
+                        {
+                            Assert.True(actualValues.ContainsKey(value));
+                            Assert.AreEqual(expected.Counts[dim][i], (int)actualValues[value]);
+                            setCount++;
+                        }
+                        else
+                        {
+                            Assert.False(actualValues.ContainsKey(value));
+                        }
+                    }
+                    Assert.AreEqual(setCount, actualValues.Count);
+                }
+            }
+        }
+
+        [Test]
+        public virtual void TestEmptyIndex()
+        {
+            // LUCENE-5045: make sure DrillSideways works with an empty index
+            Directory dir = NewDirectory();
+            Directory taxoDir = NewDirectory();
+            var writer = new RandomIndexWriter(Random(), dir);
+            var taxoWriter = new DirectoryTaxonomyWriter(taxoDir, IndexWriterConfig.OpenMode_e.CREATE);
+            IndexSearcher searcher = NewSearcher(writer.Reader);
+            var taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+
+            // Count "Author"
+            FacetsConfig config = new FacetsConfig();
+            DrillSideways ds = new DrillSideways(searcher, config, taxoReader);
+            DrillDownQuery ddq = new DrillDownQuery(config);
+            ddq.Add("Author", "Lisa");
+
+            DrillSidewaysResult r = ds.Search(ddq, 10); // this used to fail on IllegalArgEx
+            Assert.AreEqual(0, r.Hits.TotalHits);
+
+            r = ds.Search(ddq, null, null, 10, new Sort(new SortField("foo", SortField.Type_e.INT)), false, false); // this used to fail on IllegalArgEx
+            Assert.AreEqual(0, r.Hits.TotalHits);
+
+            IOUtils.Close(writer, taxoWriter, searcher.IndexReader, taxoReader, dir, taxoDir);
+        }
+    }
+
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/TestFacetsConfig.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/TestFacetsConfig.cs b/src/Lucene.Net.Tests/core/Facet/TestFacetsConfig.cs
new file mode 100644
index 0000000..f9de7c7
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/TestFacetsConfig.cs
@@ -0,0 +1,131 @@
+using Lucene.Net.Support;
+using NUnit.Framework;
+
+namespace Lucene.Net.Facet
+{
+
+    /*
+     * 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 MockAnalyzer = Lucene.Net.Analysis.MockAnalyzer;
+    using Document = Lucene.Net.Documents.Document;
+    using DirectoryTaxonomyReader = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyReader;
+    using DirectoryTaxonomyWriter = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter;
+    using DirectoryReader = Lucene.Net.Index.DirectoryReader;
+    using IndexWriter = Lucene.Net.Index.IndexWriter;
+    using IndexSearcher = Lucene.Net.Search.IndexSearcher;
+    using MatchAllDocsQuery = Lucene.Net.Search.MatchAllDocsQuery;
+    using Directory = Lucene.Net.Store.Directory;
+    using IOUtils = Lucene.Net.Util.IOUtils;
+    using TestUtil = Lucene.Net.Util.TestUtil;
+
+    public class TestFacetsConfig : FacetTestCase
+    {
+
+        [Test]
+        public virtual void TestPathToStringAndBack()
+        {
+            int iters = AtLeast(1000);
+            for (int i = 0; i < iters; i++)
+            {
+                int numParts = TestUtil.NextInt(Random(), 1, 6);
+                string[] parts = new string[numParts];
+                for (int j = 0; j < numParts; j++)
+                {
+                    string s;
+                    while (true)
+                    {
+                        s = TestUtil.RandomUnicodeString(Random());
+                        if (s.Length > 0)
+                        {
+                            break;
+                        }
+                    }
+                    parts[j] = s;
+                }
+
+                string s1 = FacetsConfig.PathToString(parts);
+                string[] parts2 = FacetsConfig.StringToPath(s1);
+                Assert.True(Arrays.Equals(parts, parts2));
+            }
+        }
+
+        [Test]
+        public virtual void TestAddSameDocTwice()
+        {
+            // LUCENE-5367: this was a problem with the previous code, making sure it
+            // works with the new code.
+            Directory indexDir = NewDirectory(), taxoDir = NewDirectory();
+            IndexWriter indexWriter = new IndexWriter(indexDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())));
+            DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir);
+            FacetsConfig facetsConfig = new FacetsConfig();
+            Document doc = new Document();
+            doc.Add(new FacetField("a", "b"));
+            doc = facetsConfig.Build(taxoWriter, doc);
+            // these two addDocument() used to fail
+            indexWriter.AddDocument(doc);
+            indexWriter.AddDocument(doc);
+            IOUtils.Close(indexWriter, taxoWriter);
+
+            DirectoryReader indexReader = DirectoryReader.Open(indexDir);
+            DirectoryTaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoDir);
+            IndexSearcher searcher = NewSearcher(indexReader);
+            FacetsCollector fc = new FacetsCollector();
+            searcher.Search(new MatchAllDocsQuery(), fc);
+
+            Facets facets = GetTaxonomyFacetCounts(taxoReader, facetsConfig, fc);
+            FacetResult res = facets.GetTopChildren(10, "a");
+            Assert.AreEqual(1, res.labelValues.Length);
+            Assert.AreEqual(2, res.labelValues[0].value);
+            IOUtils.Close(indexReader, taxoReader);
+
+            IOUtils.Close(indexDir, taxoDir);
+        }
+
+        /// <summary>
+        /// LUCENE-5479 
+        /// </summary>
+        [Test]
+        public virtual void TestCustomDefault()
+        {
+            FacetsConfig config = new FacetsConfigAnonymousInnerClassHelper(this);
+
+            Assert.True(config.GetDimConfig("foobar").hierarchical);
+        }
+
+        private class FacetsConfigAnonymousInnerClassHelper : FacetsConfig
+        {
+            private readonly TestFacetsConfig outerInstance;
+
+            public FacetsConfigAnonymousInnerClassHelper(TestFacetsConfig outerInstance)
+            {
+                this.outerInstance = outerInstance;
+            }
+
+            protected override DimConfig DefaultDimConfig
+            {
+                get
+                {
+                    DimConfig config = new DimConfig();
+                    config.hierarchical = true;
+                    return config;
+                }
+            }
+        }
+    }
+
+}
\ No newline at end of file


[20/26] lucenenet git commit: fix old sbyte

Posted by sy...@apache.org.
fix old sbyte


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

Branch: refs/heads/master
Commit: 70e1c54dd5c3f40961e251f031c38820da8bce09
Parents: e01bbc0
Author: eladmarg <el...@gmail.com>
Authored: Wed Nov 12 18:48:14 2014 -0800
Committer: eladmarg <el...@gmail.com>
Committed: Wed Nov 12 18:48:14 2014 -0800

----------------------------------------------------------------------
 Lucene.Net.Facet/FacetsConfig.cs                | 42 ++++++++++----------
 .../Taxonomy/DocValuesOrdinalsReader.cs         |  4 +-
 .../Taxonomy/FastTaxonomyFacetCounts.cs         |  6 +--
 .../Taxonomy/IntAssociationFacetField.cs        | 11 ++---
 .../TaxonomyFacetSumFloatAssociations.cs        |  2 +-
 .../Taxonomy/TaxonomyFacetSumIntAssociations.cs |  2 +-
 6 files changed, 34 insertions(+), 33 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucenenet/blob/70e1c54d/Lucene.Net.Facet/FacetsConfig.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/FacetsConfig.cs b/Lucene.Net.Facet/FacetsConfig.cs
index b8f8140..ae4ac36 100644
--- a/Lucene.Net.Facet/FacetsConfig.cs
+++ b/Lucene.Net.Facet/FacetsConfig.cs
@@ -487,7 +487,7 @@ namespace Lucene.Net.Facet
         {
             foreach (KeyValuePair<string, IList<AssociationFacetField>> ent in byField)
             {
-                sbyte[] bytes = new sbyte[16];
+                byte[] bytes = new byte[16];
                 int upto = 0;
                 string indexFieldName = ent.Key;
                 foreach (AssociationFacetField field in ent.Value)
@@ -501,10 +501,10 @@ namespace Lucene.Net.Facet
                         bytes = ArrayUtil.Grow(bytes, upto + 4);
                     }
                     // big-endian:
-                    bytes[upto++] = (sbyte)(ordinal >> 24);
-                    bytes[upto++] = (sbyte)(ordinal >> 16);
-                    bytes[upto++] = (sbyte)(ordinal >> 8);
-                    bytes[upto++] = (sbyte)ordinal;
+                    bytes[upto++] = (byte)(ordinal >> 24);
+                    bytes[upto++] = (byte)(ordinal >> 16);
+                    bytes[upto++] = (byte)(ordinal >> 8);
+                    bytes[upto++] = (byte)ordinal;
                     if (upto + field.assoc.Length > bytes.Length)
                     {
                         bytes = ArrayUtil.Grow(bytes, upto + field.assoc.Length);
@@ -529,7 +529,7 @@ namespace Lucene.Net.Facet
         protected internal virtual BytesRef DedupAndEncode(IntsRef ordinals)
         {
             Array.Sort(ordinals.Ints, ordinals.Offset, ordinals.Length);
-            sbyte[] bytes = new sbyte[5 * ordinals.Length];
+            byte[] bytes = new byte[5 * ordinals.Length];
             int lastOrd = -1;
             int upto = 0;
             for (int i = 0; i < ordinals.Length; i++)
@@ -549,37 +549,37 @@ namespace Lucene.Net.Facet
                     }
                     if ((delta & ~0x7F) == 0)
                     {
-                        bytes[upto] = (sbyte)delta;
+                        bytes[upto] = (byte)delta;
                         upto++;
                     }
                     else if ((delta & ~0x3FFF) == 0)
                     {
-                        bytes[upto] = unchecked((sbyte)(0x80 | ((delta & 0x3F80) >> 7)));
-                        bytes[upto + 1] = (sbyte)(delta & 0x7F);
+                        bytes[upto] = unchecked((byte)(0x80 | ((delta & 0x3F80) >> 7)));
+                        bytes[upto + 1] = (byte)(delta & 0x7F);
                         upto += 2;
                     }
                     else if ((delta & ~0x1FFFFF) == 0)
                     {
-                        bytes[upto] = unchecked((sbyte)(0x80 | ((delta & 0x1FC000) >> 14)));
-                        bytes[upto + 1] = unchecked((sbyte)(0x80 | ((delta & 0x3F80) >> 7)));
-                        bytes[upto + 2] = (sbyte)(delta & 0x7F);
+                        bytes[upto] = unchecked((byte)(0x80 | ((delta & 0x1FC000) >> 14)));
+                        bytes[upto + 1] = unchecked((byte)(0x80 | ((delta & 0x3F80) >> 7)));
+                        bytes[upto + 2] = (byte)(delta & 0x7F);
                         upto += 3;
                     }
                     else if ((delta & ~0xFFFFFFF) == 0)
                     {
-                        bytes[upto] = unchecked((sbyte)(0x80 | ((delta & 0xFE00000) >> 21)));
-                        bytes[upto + 1] = unchecked((sbyte)(0x80 | ((delta & 0x1FC000) >> 14)));
-                        bytes[upto + 2] = unchecked((sbyte)(0x80 | ((delta & 0x3F80) >> 7)));
-                        bytes[upto + 3] = (sbyte)(delta & 0x7F);
+                        bytes[upto] = unchecked((byte)(0x80 | ((delta & 0xFE00000) >> 21)));
+                        bytes[upto + 1] = unchecked((byte)(0x80 | ((delta & 0x1FC000) >> 14)));
+                        bytes[upto + 2] = unchecked((byte)(0x80 | ((delta & 0x3F80) >> 7)));
+                        bytes[upto + 3] = (byte)(delta & 0x7F);
                         upto += 4;
                     }
                     else
                     {
-                        bytes[upto] = unchecked((sbyte)(0x80 | ((delta & 0xF0000000) >> 28)));
-                        bytes[upto + 1] = unchecked((sbyte)(0x80 | ((delta & 0xFE00000) >> 21)));
-                        bytes[upto + 2] = unchecked((sbyte)(0x80 | ((delta & 0x1FC000) >> 14)));
-                        bytes[upto + 3] = unchecked((sbyte)(0x80 | ((delta & 0x3F80) >> 7)));
-                        bytes[upto + 4] = (sbyte)(delta & 0x7F);
+                        bytes[upto] = unchecked((byte)(0x80 | ((delta & 0xF0000000) >> 28)));
+                        bytes[upto + 1] = unchecked((byte)(0x80 | ((delta & 0xFE00000) >> 21)));
+                        bytes[upto + 2] = unchecked((byte)(0x80 | ((delta & 0x1FC000) >> 14)));
+                        bytes[upto + 3] = unchecked((byte)(0x80 | ((delta & 0x3F80) >> 7)));
+                        bytes[upto + 4] = (byte)(delta & 0x7F);
                         upto += 5;
                     }
                     lastOrd = ord;

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/70e1c54d/Lucene.Net.Facet/Taxonomy/DocValuesOrdinalsReader.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/DocValuesOrdinalsReader.cs b/Lucene.Net.Facet/Taxonomy/DocValuesOrdinalsReader.cs
index 72a68ed..3d50275 100644
--- a/Lucene.Net.Facet/Taxonomy/DocValuesOrdinalsReader.cs
+++ b/Lucene.Net.Facet/Taxonomy/DocValuesOrdinalsReader.cs
@@ -111,8 +111,8 @@
             int prev = 0;
             while (offset < upto)
             {
-                sbyte b = buf.Bytes[offset++];
-                if (b >= 0)
+                byte b = buf.Bytes[offset++];
+                if ((sbyte)b >= 0)
                 {
                     ordinals.Ints[ordinals.Length] = ((value << 7) | b) + prev;
                     value = 0;

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/70e1c54d/Lucene.Net.Facet/Taxonomy/FastTaxonomyFacetCounts.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/FastTaxonomyFacetCounts.cs b/Lucene.Net.Facet/Taxonomy/FastTaxonomyFacetCounts.cs
index 01545f0..f0d5fa0 100644
--- a/Lucene.Net.Facet/Taxonomy/FastTaxonomyFacetCounts.cs
+++ b/Lucene.Net.Facet/Taxonomy/FastTaxonomyFacetCounts.cs
@@ -76,15 +76,15 @@ namespace Lucene.Net.Facet.Taxonomy
                 while ((doc = docs.NextDoc()) != DocIdSetIterator.NO_MORE_DOCS)
                 {
                     dv.Get(doc,bytesRef);
-                    sbyte[] bytes = bytesRef.Bytes;
+                    var bytes = bytesRef.Bytes;
                     int end = bytesRef.Offset + bytesRef.Length;
                     int ord = 0;
                     int offset = bytesRef.Offset;
                     int prev = 0;
                     while (offset < end)
                     {
-                        sbyte b = bytes[offset++];
-                        if (b >= 0)
+                        byte b = bytes[offset++];
+                        if ((sbyte)b >= 0)
                         {
                             prev = ord = ((ord << 7) | b) + prev;
                             ++values[ord];

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/70e1c54d/Lucene.Net.Facet/Taxonomy/IntAssociationFacetField.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/IntAssociationFacetField.cs b/Lucene.Net.Facet/Taxonomy/IntAssociationFacetField.cs
index b3f1ddd..61d8177 100644
--- a/Lucene.Net.Facet/Taxonomy/IntAssociationFacetField.cs
+++ b/Lucene.Net.Facet/Taxonomy/IntAssociationFacetField.cs
@@ -49,12 +49,13 @@ namespace Lucene.Net.Facet.Taxonomy
         /// </summary>
         public static BytesRef intToBytesRef(int v)
         {
-            sbyte[] bytes = new sbyte[4];
+            
+            byte[] bytes = new byte[4];
             // big-endian:
-            bytes[0] = (sbyte)(v >> 24);
-            bytes[1] = (sbyte)(v >> 16);
-            bytes[2] = (sbyte)(v >> 8);
-            bytes[3] = (sbyte)v;
+            bytes[0] = (byte)(v >> 24);
+            bytes[1] = (byte)(v >> 16);
+            bytes[2] = (byte)(v >> 8);
+            bytes[3] = (byte)v;
             return new BytesRef(bytes);
         }
 

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/70e1c54d/Lucene.Net.Facet/Taxonomy/TaxonomyFacetSumFloatAssociations.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/TaxonomyFacetSumFloatAssociations.cs b/Lucene.Net.Facet/Taxonomy/TaxonomyFacetSumFloatAssociations.cs
index bf0d417..7d5188e 100644
--- a/Lucene.Net.Facet/Taxonomy/TaxonomyFacetSumFloatAssociations.cs
+++ b/Lucene.Net.Facet/Taxonomy/TaxonomyFacetSumFloatAssociations.cs
@@ -79,7 +79,7 @@ namespace Lucene.Net.Facet.Taxonomy
                     // BytesRef getAssociation()?
                     BytesRef bytesRef = new BytesRef();
                     dv.Get(doc, bytesRef);
-                    sbyte[] bytes = bytesRef.Bytes;
+                    byte[] bytes = bytesRef.Bytes;
                     int end = bytesRef.Offset + bytesRef.Length;
                     int offset = bytesRef.Offset;
                     while (offset < end)

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/70e1c54d/Lucene.Net.Facet/Taxonomy/TaxonomyFacetSumIntAssociations.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/TaxonomyFacetSumIntAssociations.cs b/Lucene.Net.Facet/Taxonomy/TaxonomyFacetSumIntAssociations.cs
index 849cb30..9942be6 100644
--- a/Lucene.Net.Facet/Taxonomy/TaxonomyFacetSumIntAssociations.cs
+++ b/Lucene.Net.Facet/Taxonomy/TaxonomyFacetSumIntAssociations.cs
@@ -77,7 +77,7 @@ namespace Lucene.Net.Facet.Taxonomy
                     // BytesRef getAssociation()?
                     BytesRef bytesRef = new BytesRef();
                     dv.Get(doc, bytesRef);
-                    sbyte[] bytes = bytesRef.Bytes;
+                    byte[] bytes = bytesRef.Bytes;
                     int end = bytesRef.Offset + bytesRef.Length;
                     int offset = bytesRef.Offset;
                     while (offset < end)


[02/26] lucenenet git commit: Revert "Revert "Updating Lucene.Net.Codes/Pulsing""

Posted by sy...@apache.org.
Revert "Revert "Updating Lucene.Net.Codes/Pulsing""

This reverts commit ce4970c8b6beb9b3fff8797a2bcaada4a930269d.


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

Branch: refs/heads/master
Commit: 6d9a6ffac2f0ea597d60415d6ef97bf08dce31f4
Parents: ce4970c
Author: eladmarg <el...@gmail.com>
Authored: Fri Nov 7 18:33:23 2014 -0800
Committer: eladmarg <el...@gmail.com>
Committed: Fri Nov 7 18:33:23 2014 -0800

----------------------------------------------------------------------
 .../Pulsing/Pulsing41PostingsFormat.cs          |   2 +-
 .../Pulsing/PulsingPostingsFormat.cs            |  10 +-
 .../Pulsing/PulsingPostingsReader.cs            | 486 +++++++++----------
 .../Pulsing/PulsingPostingsWriter.cs            | 319 ++++++------
 src/Lucene.Net.Core/Codecs/PostingsConsumer.cs  |   2 +-
 5 files changed, 384 insertions(+), 435 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucenenet/blob/6d9a6ffa/src/Lucene.Net.Codecs/Pulsing/Pulsing41PostingsFormat.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Codecs/Pulsing/Pulsing41PostingsFormat.cs b/src/Lucene.Net.Codecs/Pulsing/Pulsing41PostingsFormat.cs
index f2634f4..458771e 100644
--- a/src/Lucene.Net.Codecs/Pulsing/Pulsing41PostingsFormat.cs
+++ b/src/Lucene.Net.Codecs/Pulsing/Pulsing41PostingsFormat.cs
@@ -17,7 +17,7 @@
 
 namespace Lucene.Net.Codecs.Pulsing
 {
-    using Lucene.Net.Codecs.Lucene41;
+    using Lucene41;
 
     /// <summary>
     /// Concrete pulsing implementation over {@link Lucene41PostingsFormat}.

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/6d9a6ffa/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsFormat.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsFormat.cs b/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsFormat.cs
index 552ecbc..9ab1706 100644
--- a/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsFormat.cs
+++ b/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsFormat.cs
@@ -15,14 +15,12 @@
  * limitations under the License.
  */
 
-
-
 namespace Lucene.Net.Codecs.Pulsing
 {
     using System;
     using System.Diagnostics;
-    using Lucene.Net.Index;
-    using Lucene.Net.Util;
+    using Index;
+    using Util;
 
     /// <summary>
     /// This postings format "inlines" the postings for terms that have
@@ -49,11 +47,11 @@ namespace Lucene.Net.Codecs.Pulsing
         {
         }
 
-        /// <summary>Terms with freq <= freqCutoff are inlined into terms dict.</summary>
+        /// <summary>Terms with freq less than or equal freqCutoff are inlined into terms dict.</summary>
         protected PulsingPostingsFormat(String name, PostingsBaseFormat wrappedPostingsBaseFormat, int freqCutoff,
             int minBlockSize, int maxBlockSize) : base(name)
         {
-            Debug.Debug.Assert((minBlockSize > 1);
+            Debug.Assert(minBlockSize > 1);
 
             _freqCutoff = freqCutoff;
             _minBlockSize = minBlockSize;

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/6d9a6ffa/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsReader.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsReader.cs b/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsReader.cs
index 9f4599b..75334e6 100644
--- a/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsReader.cs
+++ b/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsReader.cs
@@ -21,9 +21,9 @@ namespace Lucene.Net.Codecs.Pulsing
     using System;
     using System.Collections.Generic;
     using System.Diagnostics;
-    using Lucene.Net.Index;
-    using Lucene.Net.Store;
-    using Lucene.Net.Util;
+    using Index;
+    using Store;
+    using Util;
 
     /// <summary>
     /// Concrete class that reads the current doc/freq/skip postings format 
@@ -39,52 +39,52 @@ namespace Lucene.Net.Codecs.Pulsing
 
         // Fallback reader for non-pulsed terms:
         private readonly PostingsReaderBase _wrappedPostingsReader;
-        private readonly SegmentReadState segmentState;
-        private int maxPositions;
-        private int version;
-        private SortedDictionary<int, int> fields;
+        private readonly SegmentReadState _segmentState;
+        private int _maxPositions;
+        private int _version;
+        private SortedDictionary<int, int> _fields;
 
         public PulsingPostingsReader(SegmentReadState state, PostingsReaderBase wrappedPostingsReader)
         {
-            this._wrappedPostingsReader = wrappedPostingsReader;
-            this.segmentState = state;
+            _wrappedPostingsReader = wrappedPostingsReader;
+            _segmentState = state;
         }
 
         public override void Init(IndexInput termsIn)
         {
-            version = CodecUtil.CheckHeader(termsIn, PulsingPostingsWriter.CODEC,
+            _version = CodecUtil.CheckHeader(termsIn, PulsingPostingsWriter.CODEC,
                 PulsingPostingsWriter.VERSION_START,
                 PulsingPostingsWriter.VERSION_CURRENT);
 
-            maxPositions = termsIn.ReadVInt();
+            _maxPositions = termsIn.ReadVInt();
             _wrappedPostingsReader.Init(termsIn);
 
-            if (_wrappedPostingsReader is PulsingPostingsReader || version < PulsingPostingsWriter.VERSION_META_ARRAY)
+            if (_wrappedPostingsReader is PulsingPostingsReader || _version < PulsingPostingsWriter.VERSION_META_ARRAY)
             {
-                fields = null;
+                _fields = null;
             }
             else
             {
-                fields = new SortedDictionary<int, int>();
-                String summaryFileName = IndexFileNames.SegmentFileName(segmentState.SegmentInfo.Name,
-                    segmentState.SegmentSuffix, PulsingPostingsWriter.SUMMARY_EXTENSION);
+                _fields = new SortedDictionary<int, int>();
+                var summaryFileName = IndexFileNames.SegmentFileName(_segmentState.SegmentInfo.Name,
+                    _segmentState.SegmentSuffix, PulsingPostingsWriter.SUMMARY_EXTENSION);
                 IndexInput input = null;
 
                 try
                 {
                     input =
-                        segmentState.Directory.OpenInput(summaryFileName, segmentState.Context);
+                        _segmentState.Directory.OpenInput(summaryFileName, _segmentState.Context);
                     CodecUtil.CheckHeader(input,
                         PulsingPostingsWriter.CODEC,
-                        version,
+                        _version,
                         PulsingPostingsWriter.VERSION_CURRENT);
 
-                    int numField = input.ReadVInt();
-                    for (int i = 0; i < numField; i++)
+                    var numField = input.ReadVInt();
+                    for (var i = 0; i < numField; i++)
                     {
-                        int fieldNum = input.ReadVInt();
-                        int longsSize = input.ReadVInt();
-                        fields.Add(fieldNum, longsSize);
+                        var fieldNum = input.ReadVInt();
+                        var longsSize = input.ReadVInt();
+                        _fields.Add(fieldNum, longsSize);
                     }
                 }
                 finally
@@ -96,25 +96,24 @@ namespace Lucene.Net.Codecs.Pulsing
 
         public override BlockTermState NewTermState()
         {
-            var state = new PulsingTermState {WrappedTermState = _wrappedPostingsReader.NewTermState()};
-            return state;
+            return new PulsingTermState {WrappedTermState = _wrappedPostingsReader.NewTermState()};
         }
 
         public override void DecodeTerm(long[] empty, DataInput input, FieldInfo fieldInfo, BlockTermState _termState,
             bool absolute)
         {
-            PulsingTermState termState = (PulsingTermState) _termState;
+            var termState = (PulsingTermState) _termState;
+
+            Debug.Assert(empty.Length == 0);
 
-            Debug.Debug.Assert((empty.Length == 0);
             termState.Absolute = termState.Absolute || absolute;
             // if we have positions, its total TF, otherwise its computed based on docFreq.
             // TODO Double check this is right..
-            long count = FieldInfo.IndexOptions_e.DOCS_AND_FREQS_AND_POSITIONS.CompareTo(fieldInfo.IndexOptions) <= 0
+            long count = FieldInfo.IndexOptions.DOCS_AND_FREQS_AND_POSITIONS.CompareTo(fieldInfo.FieldIndexOptions) <= 0
                 ? termState.TotalTermFreq
                 : termState.DocFreq;
-            //System.out.println("  count=" + count + " threshold=" + maxPositions);
-
-            if (count <= maxPositions)
+           
+            if (count <= _maxPositions)
             {
                 // Inlined into terms dict -- just read the byte[] blob in,
                 // but don't decode it now (we only decode when a DocsEnum
@@ -134,12 +133,12 @@ namespace Lucene.Net.Codecs.Pulsing
             }
             else
             {
-                int longsSize = fields == null ? 0 : fields[fieldInfo.Number];
+                var longsSize = _fields == null ? 0 : _fields[fieldInfo.Number];
                 if (termState.Longs == null)
                 {
                     termState.Longs = new long[longsSize];
                 }
-                for (int i = 0; i < longsSize; i++)
+                for (var i = 0; i < longsSize; i++)
                 {
                     termState.Longs[i] = input.ReadVLong();
                 }
@@ -156,7 +155,7 @@ namespace Lucene.Net.Codecs.Pulsing
         public override DocsEnum Docs(FieldInfo field, BlockTermState _termState, Bits liveDocs, DocsEnum reuse,
             int flags)
         {
-            PulsingTermState termState = (PulsingTermState) _termState;
+            var termState = (PulsingTermState) _termState;
             if (termState.PostingsSize != -1)
             {
                 PulsingDocsEnum postings;
@@ -171,7 +170,7 @@ namespace Lucene.Net.Codecs.Pulsing
                 else
                 {
                     // the 'reuse' is actually the wrapped enum
-                    PulsingDocsEnum previous = (PulsingDocsEnum) GetOther(reuse);
+                    var previous = (PulsingDocsEnum) GetOther(reuse);
                     if (previous != null && previous.CanReuse(field))
                     {
                         postings = previous;
@@ -181,26 +180,21 @@ namespace Lucene.Net.Codecs.Pulsing
                         postings = new PulsingDocsEnum(field);
                     }
                 }
+                
                 if (reuse != postings)
-                {
                     SetOther(postings, reuse); // postings.other = reuse
-                }
+                
                 return postings.Reset(liveDocs, termState);
             }
-            else
-            {
-                if (reuse is PulsingDocsEnum)
-                {
-                    DocsEnum wrapped = _wrappedPostingsReader.Docs(field, termState.WrappedTermState, liveDocs,
-                        GetOther(reuse), flags);
-                    SetOther(wrapped, reuse); // wrapped.other = reuse
-                    return wrapped;
-                }
-                else
-                {
-                    return _wrappedPostingsReader.Docs(field, termState.WrappedTermState, liveDocs, reuse, flags);
-                }
-            }
+
+            if (!(reuse is PulsingDocsEnum))
+                return _wrappedPostingsReader.Docs(field, termState.WrappedTermState, liveDocs, reuse, flags);
+
+            var wrapped = _wrappedPostingsReader.Docs(field, termState.WrappedTermState, liveDocs,
+                GetOther(reuse), flags);
+
+            SetOther(wrapped, reuse); // wrapped.other = reuse
+            return wrapped;
         }
 
         public override DocsAndPositionsEnum DocsAndPositions(FieldInfo field, BlockTermState _termState, Bits liveDocs,
@@ -208,7 +202,7 @@ namespace Lucene.Net.Codecs.Pulsing
             int flags)
         {
 
-            PulsingTermState termState = (PulsingTermState) _termState;
+            var termState = (PulsingTermState) _termState;
 
             if (termState.PostingsSize != -1)
             {
@@ -224,7 +218,7 @@ namespace Lucene.Net.Codecs.Pulsing
                 else
                 {
                     // the 'reuse' is actually the wrapped enum
-                    PulsingDocsAndPositionsEnum previous = (PulsingDocsAndPositionsEnum) GetOther(reuse);
+                    var previous = (PulsingDocsAndPositionsEnum) GetOther(reuse);
                     if (previous != null && previous.CanReuse(field))
                     {
                         postings = previous;
@@ -238,25 +232,19 @@ namespace Lucene.Net.Codecs.Pulsing
                 {
                     SetOther(postings, reuse); // postings.other = reuse 
                 }
-                return postings.reset(liveDocs, termState);
-            }
-            else
-            {
-                if (reuse is PulsingDocsAndPositionsEnum)
-                {
-                    DocsAndPositionsEnum wrapped = _wrappedPostingsReader.DocsAndPositions(field,
-                        termState.WrappedTermState,
-                        liveDocs, (DocsAndPositionsEnum) GetOther(reuse),
-                        flags);
-                    SetOther(wrapped, reuse); // wrapped.other = reuse
-                    return wrapped;
-                }
-                else
-                {
-                    return _wrappedPostingsReader.DocsAndPositions(field, termState.WrappedTermState, liveDocs, reuse,
-                        flags);
-                }
+                return postings.Reset(liveDocs, termState);
             }
+
+            if (!(reuse is PulsingDocsAndPositionsEnum))
+                return _wrappedPostingsReader.DocsAndPositions(field, termState.WrappedTermState, liveDocs, reuse,
+                    flags);
+
+            var wrapped = _wrappedPostingsReader.DocsAndPositions(field,
+                termState.WrappedTermState,
+                liveDocs, (DocsAndPositionsEnum) GetOther(reuse),
+                flags);
+            SetOther(wrapped, reuse); // wrapped.other = reuse
+            return wrapped;
         }
 
         public override long RamBytesUsed()
@@ -287,14 +275,10 @@ namespace Lucene.Net.Codecs.Pulsing
         private DocsEnum GetOther(DocsEnum de)
         {
             if (de == null)
-            {
                 return null;
-            }
-            else
-            {
-                AttributeSource atts = de.Attributes();
-                return atts.AddAttribute(PulsingEnumAttribute.Enums().get(this);
-            }
+            
+            var atts = de.Attributes();
+            return atts.AddAttribute(PulsingEnumAttribute.class).Enums().get(this);
         }
 
         /// <summary>
@@ -303,8 +287,8 @@ namespace Lucene.Net.Codecs.Pulsing
         /// </summary>
         private DocsEnum SetOther(DocsEnum de, DocsEnum other)
         {
-            AttributeSource atts = de.Attributes();
-            return atts.AddAttribute(PulsingEnumAttributeImpl.Enums().put(this, other));
+            var atts = de.Attributes();
+            return atts.AddAttribute(PulsingEnumAttribute.class).Enums().put(this, other);
         }
 
         ///<summary>
@@ -329,7 +313,7 @@ namespace Lucene.Net.Codecs.Pulsing
 
             public override object Clone()
             {
-                PulsingTermState clone = (PulsingTermState) base.Clone();
+                var clone = (PulsingTermState) base.Clone();
                 if (PostingsSize != -1)
                 {
                     clone.Postings = new byte[PostingsSize];
@@ -337,14 +321,14 @@ namespace Lucene.Net.Codecs.Pulsing
                 }
                 else
                 {
-                    Debug.Debug.Assert((WrappedTermState != null);
+                    Debug.Assert(WrappedTermState != null);
                     clone.WrappedTermState = (BlockTermState) WrappedTermState.Clone();
                     clone.Absolute = Absolute;
-                    if (Longs != null)
-                    {
-                        clone.Longs = new long[Longs.Length];
-                        Array.Copy(Longs, 0, clone.Longs, 0, Longs.Length);
-                    }
+                    
+                    if (Longs == null) return clone;
+
+                    clone.Longs = new long[Longs.Length];
+                    Array.Copy(Longs, 0, clone.Longs, 0, Longs.Length);
                 }
                 return clone;
             }
@@ -360,7 +344,7 @@ namespace Lucene.Net.Codecs.Pulsing
                     {
                         Postings = new byte[ArrayUtil.Oversize(_other.PostingsSize, 1)];
                     }
-                    System.Array.Copy(_other.Postings, 0, Postings, 0, _other.PostingsSize);
+                    Array.Copy(_other.Postings, 0, Postings, 0, _other.PostingsSize);
                 }
                 else
                 {
@@ -371,373 +355,341 @@ namespace Lucene.Net.Codecs.Pulsing
             public override String ToString()
             {
                 if (PostingsSize == -1)
-                {
                     return "PulsingTermState: not inlined: wrapped=" + WrappedTermState;
-                }
-                else
-                {
-                    return "PulsingTermState: inlined size=" + PostingsSize + " " + base.ToString();
-                }
+                
+                return "PulsingTermState: inlined size=" + PostingsSize + " " + base.ToString();
             }
         }
 
         internal class PulsingDocsEnum : DocsEnum
         {
-            private byte[] postingsBytes;
-            private readonly ByteArrayDataInput postings = new ByteArrayDataInput();
-            private readonly FieldInfo.IndexOptions_e? indexOptions;
-            private readonly bool storePayloads;
-            private readonly bool storeOffsets;
-            private Bits liveDocs;
-
-            private int docID = -1;
-            private int accum;
-            private int freq;
-            private int payloadLength;
-            private int cost;
+            private byte[] _postingsBytes;
+            private readonly ByteArrayDataInput _postings = new ByteArrayDataInput();
+            private readonly FieldInfo.IndexOptions? _indexOptions;
+            private readonly bool _storePayloads;
+            private readonly bool _storeOffsets;
+            private Bits _liveDocs;
+
+            private int _docId = -1;
+            private int _accum;
+            private int _freq;
+            private int _payloadLength;
+            private int _cost;
 
             public PulsingDocsEnum(FieldInfo fieldInfo)
             {
-                indexOptions = fieldInfo.IndexOptions;
-                storePayloads = fieldInfo.HasPayloads();
-                storeOffsets = indexOptions.Value.CompareTo(FieldInfo.IndexOptions_e.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS) >= 0;
+                _indexOptions = fieldInfo.FieldIndexOptions;
+                _storePayloads = fieldInfo.HasPayloads();
+                _storeOffsets = _indexOptions.Value.CompareTo(FieldInfo.IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS) >= 0;
             }
 
             public PulsingDocsEnum Reset(Bits liveDocs, PulsingTermState termState)
             {
-                Debug.Debug.Assert((termState.PostingsSize != -1);
+                Debug.Assert(termState.PostingsSize != -1);
 
                 // Must make a copy of termState's byte[] so that if
                 // app does TermsEnum.next(), this DocsEnum is not affected
-                if (postingsBytes == null)
+                if (_postingsBytes == null)
                 {
-                    postingsBytes = new byte[termState.PostingsSize];
+                    _postingsBytes = new byte[termState.PostingsSize];
                 }
-                else if (postingsBytes.Length < termState.PostingsSize)
+                else if (_postingsBytes.Length < termState.PostingsSize)
                 {
-                    postingsBytes = ArrayUtil.Grow(postingsBytes, termState.PostingsSize);
+                    _postingsBytes = ArrayUtil.Grow(_postingsBytes, termState.PostingsSize);
                 }
-                System.Array.Copy(termState.Postings, 0, postingsBytes, 0, termState.PostingsSize);
-                postings.Reset(postingsBytes, 0, termState.PostingsSize);
-                docID = -1;
-                accum = 0;
-                freq = 1;
-                cost = termState.DocFreq;
-                payloadLength = 0;
-                this.liveDocs = liveDocs;
+                System.Array.Copy(termState.Postings, 0, _postingsBytes, 0, termState.PostingsSize);
+                _postings.Reset(_postingsBytes, 0, termState.PostingsSize);
+                _docId = -1;
+                _accum = 0;
+                _freq = 1;
+                _cost = termState.DocFreq;
+                _payloadLength = 0;
+                this._liveDocs = liveDocs;
                 return this;
             }
 
             public bool CanReuse(FieldInfo fieldInfo)
             {
-                return indexOptions == fieldInfo.IndexOptions && storePayloads == fieldInfo.HasPayloads();
+                return _indexOptions == fieldInfo.FieldIndexOptions && _storePayloads == fieldInfo.HasPayloads();
             }
 
             public override int DocID()
             {
-                return docID;
+                return _docId;
             }
 
             public override int NextDoc()
             {
-                //System.out.println("PR nextDoc this= "+ this);
                 while (true)
                 {
-                    if (postings.Eof())
+                    if (_postings.Eof())
+                        return _docId = NO_MORE_DOCS;
+                    
+                    var code = _postings.ReadVInt();
+                    if (_indexOptions == FieldInfo.IndexOptions.DOCS_ONLY)
                     {
-                        return docID = NO_MORE_DOCS;
-                    }
-
-                    int code = postings.ReadVInt();
-                    if (indexOptions == FieldInfo.IndexOptions_e.DOCS_ONLY)
-                    {
-                        accum += code;
+                        _accum += code;
                     }
                     else
                     {
-                        accum += (int)((uint)code >> 1); ; // shift off low bit
-                        if ((code & 1) != 0)
-                        {
-                            // if low bit is set
-                            freq = 1; // freq is one
-                        }
-                        else
-                        {
-                            freq = postings.ReadVInt(); // else read freq
-                        }
+                        _accum += (int)((uint)code >> 1); ; // shift off low bit
+                        _freq = (code & 1) != 0 ? 1 : _postings.ReadVInt();
 
-                        if (indexOptions.Value.CompareTo(FieldInfo.IndexOptions_e.DOCS_AND_FREQS_AND_POSITIONS) >= 0)
+                        if (_indexOptions.Value.CompareTo(FieldInfo.IndexOptions.DOCS_AND_FREQS_AND_POSITIONS) >= 0)
                         {
                             // Skip positions
-                            if (storePayloads)
+                            if (_storePayloads)
                             {
-                                for (int pos = 0; pos < freq; pos++)
+                                for (var pos = 0; pos < _freq; pos++)
                                 {
-                                    int posCode = postings.ReadVInt();
+                                    var posCode = _postings.ReadVInt();
                                     if ((posCode & 1) != 0)
                                     {
-                                        payloadLength = postings.ReadVInt();
+                                        _payloadLength = _postings.ReadVInt();
                                     }
-                                    if (storeOffsets && (postings.ReadVInt() & 1) != 0)
+                                    if (_storeOffsets && (_postings.ReadVInt() & 1) != 0)
                                     {
                                         // new offset length
-                                        postings.ReadVInt();
+                                        _postings.ReadVInt();
                                     }
-                                    if (payloadLength != 0)
+                                    if (_payloadLength != 0)
                                     {
-                                        postings.SkipBytes(payloadLength);
+                                        _postings.SkipBytes(_payloadLength);
                                     }
                                 }
                             }
                             else
                             {
-                                for (int pos = 0; pos < freq; pos++)
+                                for (var pos = 0; pos < _freq; pos++)
                                 {
                                     // TODO: skipVInt
-                                    postings.ReadVInt();
-                                    if (storeOffsets && (postings.ReadVInt() & 1) != 0)
+                                    _postings.ReadVInt();
+                                    if (_storeOffsets && (_postings.ReadVInt() & 1) != 0)
                                     {
                                         // new offset length
-                                        postings.ReadVInt();
+                                        _postings.ReadVInt();
                                     }
                                 }
                             }
                         }
                     }
 
-                    if (liveDocs == null || liveDocs.Get(accum))
-                    {
-                        return (docID = accum);
-                    }
-
+                    if (_liveDocs == null || _liveDocs.Get(_accum))
+                        return (_docId = _accum);
                 }
             }
 
             public override int Advance(int target)
             {
-                return docID = SlowAdvance(target);
+                return _docId = SlowAdvance(target);
             }
 
             public override long Cost()
             {
-                return cost;
+                return _cost;
             }
 
             public override int Freq()
             {
-                return freq;
+                return _freq;
             }
         }
 
         internal class PulsingDocsAndPositionsEnum : DocsAndPositionsEnum
         {
-            private byte[] postingsBytes;
-            private readonly ByteArrayDataInput postings = new ByteArrayDataInput();
-            private readonly bool storePayloads;
-            private readonly bool storeOffsets;
+            private byte[] _postingsBytes;
+            private readonly ByteArrayDataInput _postings = new ByteArrayDataInput();
+            private readonly bool _storePayloads;
+            private readonly bool _storeOffsets;
             // note: we could actually reuse across different options, if we passed this to reset()
             // and re-init'ed storeOffsets accordingly (made it non-final)
-            private readonly FieldInfo.IndexOptions_e? indexOptions;
-
-            private Bits liveDocs;
-            private int docID = -1;
-            private int accum;
-            private int freq;
-            private int posPending;
-            private int position;
-            private int payloadLength;
-            private BytesRef payload;
-            private int startOffset;
-            private int offsetLength;
-
-            private bool payloadRetrieved;
-            private int cost;
+            private readonly FieldInfo.IndexOptions? _indexOptions;
+
+            private Bits _liveDocs;
+            private int _docId = -1;
+            private int _accum;
+            private int _freq;
+            private int _posPending;
+            private int _position;
+            private int _payloadLength;
+            private BytesRef _payload;
+            private int _startOffset;
+            private int _offsetLength;
+
+            private bool _payloadRetrieved;
+            private int _cost;
 
             public PulsingDocsAndPositionsEnum(FieldInfo fieldInfo)
             {
-                indexOptions = fieldInfo.IndexOptions;
-                storePayloads = fieldInfo.HasPayloads();
-                storeOffsets =
-                    indexOptions.Value.CompareTo(FieldInfo.IndexOptions_e.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS) >= 0;
+                _indexOptions = fieldInfo.FieldIndexOptions;
+                _storePayloads = fieldInfo.HasPayloads();
+                _storeOffsets =
+                    _indexOptions.Value.CompareTo(FieldInfo.IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS) >= 0;
             }
 
-            public PulsingDocsAndPositionsEnum reset(Bits liveDocs, PulsingTermState termState)
+            public PulsingDocsAndPositionsEnum Reset(Bits liveDocs, PulsingTermState termState)
             {
-                Debug.Debug.Assert((termState.PostingsSize != -1);
+                Debug.Assert(termState.PostingsSize != -1);
 
-                if (postingsBytes == null)
+                if (_postingsBytes == null)
                 {
-                    postingsBytes = new byte[termState.PostingsSize];
+                    _postingsBytes = new byte[termState.PostingsSize];
                 }
-                else if (postingsBytes.Length < termState.PostingsSize)
+                else if (_postingsBytes.Length < termState.PostingsSize)
                 {
-                    postingsBytes = ArrayUtil.Grow(postingsBytes, termState.PostingsSize);
+                    _postingsBytes = ArrayUtil.Grow(_postingsBytes, termState.PostingsSize);
                 }
 
-                System.Array.Copy(termState.Postings, 0, postingsBytes, 0, termState.PostingsSize);
-                postings.Reset(postingsBytes, 0, termState.PostingsSize);
-                this.liveDocs = liveDocs;
-                payloadLength = 0;
-                posPending = 0;
-                docID = -1;
-                accum = 0;
-                cost = termState.DocFreq;
-                startOffset = storeOffsets ? 0 : -1; // always return -1 if no offsets are stored
-                offsetLength = 0;
+                Array.Copy(termState.Postings, 0, _postingsBytes, 0, termState.PostingsSize);
+                _postings.Reset(_postingsBytes, 0, termState.PostingsSize);
+                this._liveDocs = liveDocs;
+                _payloadLength = 0;
+                _posPending = 0;
+                _docId = -1;
+                _accum = 0;
+                _cost = termState.DocFreq;
+                _startOffset = _storeOffsets ? 0 : -1; // always return -1 if no offsets are stored
+                _offsetLength = 0;
                 //System.out.println("PR d&p reset storesPayloads=" + storePayloads + " bytes=" + bytes.length + " this=" + this);
                 return this;
             }
 
             public bool CanReuse(FieldInfo fieldInfo)
             {
-                return indexOptions == fieldInfo.IndexOptions && storePayloads == fieldInfo.HasPayloads();
+                return _indexOptions == fieldInfo.FieldIndexOptions && _storePayloads == fieldInfo.HasPayloads();
             }
 
             public override int NextDoc()
             {
-
                 while (true)
                 {
-
                     SkipPositions();
 
-                    if (postings.Eof())
+                    if (_postings.Eof())
                     {
-                        return docID = NO_MORE_DOCS;
+                        return _docId = NO_MORE_DOCS;
                     }
 
-                    int code = postings.ReadVInt();
-                    accum += (int)((uint)code >> 1); // shift off low bit 
-                    if ((code & 1) != 0)
-                    {
-                        // if low bit is set
-                        freq = 1; // freq is one
-                    }
-                    else
-                    {
-                        freq = postings.ReadVInt(); // else read freq
-                    }
-                    posPending = freq;
-                    startOffset = storeOffsets ? 0 : -1; // always return -1 if no offsets are stored
+                    var code = _postings.ReadVInt();
+                    _accum += (int)((uint)code >> 1); // shift off low bit 
+                    _freq = (code & 1) != 0 ? 1 : _postings.ReadVInt();
+                    _posPending = _freq;
+                    _startOffset = _storeOffsets ? 0 : -1; // always return -1 if no offsets are stored
 
-                    if (liveDocs == null || liveDocs.Get(accum))
-                    {
-                        position = 0;
-                        return (docID = accum);
-                    }
+                    if (_liveDocs != null && !_liveDocs.Get(_accum)) continue;
+
+                    _position = 0;
+                    return (_docId = _accum);
                 }
             }
 
             public override int Freq()
             {
-                return freq;
+                return _freq;
             }
 
             public override int DocID()
             {
-                return docID;
+                return _docId;
             }
 
             public override int Advance(int target)
             {
-                return docID = SlowAdvance(target);
+                return _docId = SlowAdvance(target);
             }
 
             public override int NextPosition()
             {
-                Debug.Debug.Assert((posPending > 0);
+                Debug.Assert(_posPending > 0);
 
-                posPending--;
+                _posPending--;
 
-                if (storePayloads)
+                if (_storePayloads)
                 {
-                    if (!payloadRetrieved)
+                    if (!_payloadRetrieved)
                     {
-                        postings.SkipBytes(payloadLength);
+                        _postings.SkipBytes(_payloadLength);
                     }
-                    int code = postings.ReadVInt();
+                    int code = _postings.ReadVInt();
                     if ((code & 1) != 0)
                     {
-                        payloadLength = postings.ReadVInt();
+                        _payloadLength = _postings.ReadVInt();
                     }
-                    position += (int)((uint)code >> 1);
-                    payloadRetrieved = false;
+                    _position += (int)((uint)code >> 1);
+                    _payloadRetrieved = false;
                 }
                 else
                 {
-                    position += postings.ReadVInt();
+                    _position += _postings.ReadVInt();
                 }
 
-                if (storeOffsets)
+                if (_storeOffsets)
                 {
-                    int offsetCode = postings.ReadVInt();
+                    int offsetCode = _postings.ReadVInt();
                     if ((offsetCode & 1) != 0)
                     {
                         // new offset length
-                        offsetLength = postings.ReadVInt();
+                        _offsetLength = _postings.ReadVInt();
                     }
-                    startOffset += (int)((uint)offsetCode >> 1);
+                    _startOffset += (int)((uint)offsetCode >> 1);
                 }
 
-                return position;
+                return _position;
             }
 
             public override int StartOffset()
             {
-                return startOffset;
+                return _startOffset;
             }
 
             public override int EndOffset()
             {
-                return startOffset + offsetLength;
+                return _startOffset + _offsetLength;
             }
 
             public override BytesRef Payload
             {
                 get
                 {
-                    if (payloadRetrieved)
-                    {
-                        return payload;
-                    }
-                    else if (storePayloads && payloadLength > 0)
+                    if (_payloadRetrieved)
+                        return _payload;
+                    
+                    if (_storePayloads && _payloadLength > 0)
                     {
-                        payloadRetrieved = true;
-                        if (payload == null)
+                        _payloadRetrieved = true;
+                        if (_payload == null)
                         {
-                            payload = new BytesRef(payloadLength);
+                            _payload = new BytesRef(_payloadLength);
                         }
                         else
                         {
-                            payload.Grow(payloadLength);
+                            _payload.Grow(_payloadLength);
                         }
-                        postings.ReadBytes(payload.Bytes, 0, payloadLength);
-                        payload.Length = payloadLength;
-                        return payload;
-                    }
-                    else
-                    {
-                        return null;
+                        _postings.ReadBytes(_payload.Bytes, 0, _payloadLength);
+                        _payload.Length = _payloadLength;
+                        return _payload;
                     }
+                    
+                    return null;
                 }
             }
 
             private void SkipPositions()
             {
-                while (posPending != 0)
+                while (_posPending != 0)
                 {
                     NextPosition();
                 }
-                if (storePayloads && !payloadRetrieved)
+                if (_storePayloads && !_payloadRetrieved)
                 {
-                    postings.SkipBytes(payloadLength);
-                    payloadRetrieved = true;
+                    _postings.SkipBytes(_payloadLength);
+                    _payloadRetrieved = true;
                 }
             }
             
             public override long Cost()
             {
-                return cost;
+                return _cost;
             }
         }
         

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/6d9a6ffa/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsWriter.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsWriter.cs b/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsWriter.cs
index 528f8de..82fe52a 100644
--- a/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsWriter.cs
+++ b/src/Lucene.Net.Codecs/Pulsing/PulsingPostingsWriter.cs
@@ -15,15 +15,14 @@
  * limitations under the License.
  */
 
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using Lucene.Net.Index;
-using Lucene.Net.Store;
-using Lucene.Net.Util;
-
 namespace Lucene.Net.Codecs.Pulsing
 {
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics;
+    using Index;
+    using Store;
+    using Util;
 
     /// <summary>
     /// TODO: we now inline based on total TF of the term,
@@ -53,36 +52,36 @@ namespace Lucene.Net.Codecs.Pulsing
         internal static readonly int VERSION_META_ARRAY = 1;
         internal static readonly int VERSION_CURRENT = VERSION_META_ARRAY;
 
-        private SegmentWriteState segmentState;
-        private IndexOutput termsOut;
-        private List<FieldMetaData> fields;
-        private FieldInfo.IndexOptions_e? indexOptions;
-        private bool storePayloads;
+        private readonly SegmentWriteState _segmentState;
+        private IndexOutput _termsOut;
+        private readonly List<FieldMetaData> _fields;
+        private FieldInfo.IndexOptions? _indexOptions;
+        private bool _storePayloads;
 
         // information for wrapped PF, in current field
-        private int longsSize;
-        private long[] longs;
-        private bool absolute;
+        private int _longsSize;
+        private long[] _longs;
+        private bool _absolute;
 
         private class PulsingTermState : BlockTermState
         {
-            internal byte[] bytes;
-            internal BlockTermState wrappedState;
+            internal byte[] BYTES;
+            internal BlockTermState WRAPPED_STATE;
 
             public override String ToString()
             {
-                if (bytes != null)
+                if (BYTES != null)
                 {
                     return "inlined";
                 }
-                return "not inlined wrapped=" + wrappedState;
+                return "not inlined wrapped=" + WRAPPED_STATE;
             }
         }
 
         // one entry per position
-        private Position[] pending;
-        private int pendingCount = 0;   // -1 once we've hit too many positions
-        private Position currentDoc;    // first Position entry of current doc
+        private readonly Position[] _pending;
+        private int _pendingCount = 0;   // -1 once we've hit too many positions
+        private Position _currentDoc;    // first Position entry of current doc
 
         private sealed class Position
         {
@@ -114,43 +113,42 @@ namespace Lucene.Net.Codecs.Pulsing
 
         /// <summary>
         /// If the total number of positions (summed across all docs
-        /// for this term) is <= maxPositions, then the postings are
+        /// for this term) is less than or equal maxPositions, then the postings are
         /// inlined into terms dict
         /// </summary>
         public PulsingPostingsWriter(SegmentWriteState state, int maxPositions, PostingsWriterBase wrappedPostingsWriter)
         {
 
-            pending = new Position[maxPositions];
-            for (int i = 0; i < maxPositions; i++)
+            _pending = new Position[maxPositions];
+            for (var i = 0; i < maxPositions; i++)
             {
-                pending[i] = new Position();
+                _pending[i] = new Position();
             }
-            fields = new List<FieldMetaData>();
+            _fields = new List<FieldMetaData>();
 
             // We simply wrap another postings writer, but only call
             // on it when tot positions is >= the cutoff:
-            this._wrappedPostingsWriter = wrappedPostingsWriter;
-            this.segmentState = state;
+            _wrappedPostingsWriter = wrappedPostingsWriter;
+            _segmentState = state;
         }
 
         public override void Init(IndexOutput termsOut)
         {
-            this.termsOut = termsOut;
+            _termsOut = termsOut;
             CodecUtil.WriteHeader(termsOut, CODEC, VERSION_CURRENT);
-            termsOut.WriteVInt(pending.Length); // encode maxPositions in header
+            termsOut.WriteVInt(_pending.Length); // encode maxPositions in header
             _wrappedPostingsWriter.Init(termsOut);
         }
 
         public override BlockTermState NewTermState()
         {
-            PulsingTermState state = new PulsingTermState();
-            state.wrappedState = _wrappedPostingsWriter.NewTermState();
+            var state = new PulsingTermState {WRAPPED_STATE = _wrappedPostingsWriter.NewTermState()};
             return state;
         }
 
         public override void StartTerm()
         {
-            Debug.Debug.Assert((pendingCount == 0);
+            Debug.Assert(_pendingCount == 0);
         }
 
         /// <summary>
@@ -163,61 +161,61 @@ namespace Lucene.Net.Codecs.Pulsing
         /// <returns></returns>
         public override int SetField(FieldInfo fieldInfo)
         {
-            this.indexOptions = fieldInfo.IndexOptions;
-            storePayloads = fieldInfo.HasPayloads();
-            absolute = false;
-            longsSize = _wrappedPostingsWriter.SetField(fieldInfo);
-            longs = new long[longsSize];
-            fields.Add(new FieldMetaData(fieldInfo.Number, longsSize));
+            _indexOptions = fieldInfo.FieldIndexOptions;
+            _storePayloads = fieldInfo.HasPayloads();
+            _absolute = false;
+            _longsSize = _wrappedPostingsWriter.SetField(fieldInfo);
+            _longs = new long[_longsSize];
+            _fields.Add(new FieldMetaData(fieldInfo.Number, _longsSize));
             return 0;
         }
 
-        public override void StartDoc(int docID, int termDocFreq)
+        public override void StartDoc(int docId, int termDocFreq)
         {
-            Debug.Debug.Assert((docID >= 0, "Got DocID=" + docID);
+            Debug.Assert(docId >= 0, "Got DocID=" + docId);
 
-            if (pendingCount == pending.Length)
+            if (_pendingCount == _pending.Length)
             {
-                push();
+                Push();
                 _wrappedPostingsWriter.FinishDoc();
             }
 
-            if (pendingCount != -1)
+            if (_pendingCount != -1)
             {
-                Debug.Debug.Assert((pendingCount < pending.Length);
-                currentDoc = pending[pendingCount];
-                currentDoc.docID = docID;
-                if (indexOptions == FieldInfo.IndexOptions_e.DOCS_ONLY)
+                Debug.Assert(_pendingCount < _pending.Length);
+                _currentDoc = _pending[_pendingCount];
+                _currentDoc.docID = docId;
+                if (_indexOptions == FieldInfo.IndexOptions.DOCS_ONLY)
                 {
-                    pendingCount++;
+                    _pendingCount++;
                 }
-                else if (indexOptions == FieldInfo.IndexOptions_e.DOCS_AND_FREQS)
+                else if (_indexOptions == FieldInfo.IndexOptions.DOCS_AND_FREQS)
                 {
-                    pendingCount++;
-                    currentDoc.termFreq = termDocFreq;
+                    _pendingCount++;
+                    _currentDoc.termFreq = termDocFreq;
                 }
                 else
                 {
-                    currentDoc.termFreq = termDocFreq;
+                    _currentDoc.termFreq = termDocFreq;
                 }
             }
             else
             {
                 // We've already seen too many docs for this term --
                 // just forward to our fallback writer
-                _wrappedPostingsWriter.StartDoc(docID, termDocFreq);
+                _wrappedPostingsWriter.StartDoc(docId, termDocFreq);
             }
         }
 
         public override void AddPosition(int position, BytesRef payload, int startOffset, int endOffset)
         {
 
-            if (pendingCount == pending.Length)
+            if (_pendingCount == _pending.Length)
             {
-                push();
+                Push();
             }
 
-            if (pendingCount == -1)
+            if (_pendingCount == -1)
             {
                 // We've already seen too many docs for this term --
                 // just forward to our fallback writer
@@ -226,11 +224,11 @@ namespace Lucene.Net.Codecs.Pulsing
             else
             {
                 // buffer up
-                Position pos = pending[pendingCount++];
+                Position pos = _pending[_pendingCount++];
                 pos.pos = position;
                 pos.startOffset = startOffset;
                 pos.endOffset = endOffset;
-                pos.docID = currentDoc.docID;
+                pos.docID = _currentDoc.docID;
                 if (payload != null && payload.Length > 0)
                 {
                     if (pos.payload == null)
@@ -251,13 +249,13 @@ namespace Lucene.Net.Codecs.Pulsing
 
         public override void FinishDoc()
         {
-            if (pendingCount == -1)
+            if (_pendingCount == -1)
             {
                 _wrappedPostingsWriter.FinishDoc();
             }
         }
 
-        private readonly RAMOutputStream buffer = new RAMOutputStream();
+        private readonly RAMOutputStream _buffer = new RAMOutputStream();
 
         /// <summary>
         /// Called when we are done adding docs to this term
@@ -265,16 +263,16 @@ namespace Lucene.Net.Codecs.Pulsing
         /// <param name="_state"></param>
         public override void FinishTerm(BlockTermState _state)
         {
-            PulsingTermState state = (PulsingTermState) _state;
+            var state = (PulsingTermState) _state;
 
-            Debug.Debug.Assert((pendingCount > 0 || pendingCount == -1);
+            Debug.Assert(_pendingCount > 0 || _pendingCount == -1);
 
-            if (pendingCount == -1)
+            if (_pendingCount == -1)
             {
-                state.wrappedState.DocFreq = state.DocFreq;
-                state.wrappedState.TotalTermFreq = state.TotalTermFreq;
-                state.bytes = null;
-                _wrappedPostingsWriter.FinishTerm(state.wrappedState);
+                state.WRAPPED_STATE.DocFreq = state.DocFreq;
+                state.WRAPPED_STATE.TotalTermFreq = state.TotalTermFreq;
+                state.BYTES = null;
+                _wrappedPostingsWriter.FinishTerm(state.WRAPPED_STATE);
             }
             else
             {
@@ -288,72 +286,72 @@ namespace Lucene.Net.Codecs.Pulsing
                 // given codec wants to store other interesting
                 // stuff, it could use this pulsing codec to do so
 
-                if (indexOptions.Value.CompareTo(FieldInfo.IndexOptions_e.DOCS_AND_FREQS_AND_POSITIONS) >= 0)
+                if (_indexOptions >= FieldInfo.IndexOptions.DOCS_AND_FREQS_AND_POSITIONS)
                 {
-                    int lastDocID = 0;
-                    int pendingIDX = 0;
-                    int lastPayloadLength = -1;
-                    int lastOffsetLength = -1;
-                    while (pendingIDX < pendingCount)
+                    var lastDocID = 0;
+                    var pendingIDX = 0;
+                    var lastPayloadLength = -1;
+                    var lastOffsetLength = -1;
+                    while (pendingIDX < _pendingCount)
                     {
-                        Position doc = pending[pendingIDX];
+                        var doc = _pending[pendingIDX];
 
-                        int delta = doc.docID - lastDocID;
+                        var delta = doc.docID - lastDocID;
                         lastDocID = doc.docID;
 
                         // if (DEBUG) System.out.println("  write doc=" + doc.docID + " freq=" + doc.termFreq);
 
                         if (doc.termFreq == 1)
                         {
-                            buffer.WriteVInt((delta << 1) | 1);
+                            _buffer.WriteVInt((delta << 1) | 1);
                         }
                         else
                         {
-                            buffer.WriteVInt(delta << 1);
-                            buffer.WriteVInt(doc.termFreq);
+                            _buffer.WriteVInt(delta << 1);
+                            _buffer.WriteVInt(doc.termFreq);
                         }
 
-                        int lastPos = 0;
-                        int lastOffset = 0;
-                        for (int posIDX = 0; posIDX < doc.termFreq; posIDX++)
+                        var lastPos = 0;
+                        var lastOffset = 0;
+                        for (var posIDX = 0; posIDX < doc.termFreq; posIDX++)
                         {
-                            Position pos = pending[pendingIDX++];
-                            Debug.Debug.Assert((pos.docID == doc.docID);
-                            int posDelta = pos.pos - lastPos;
+                            var pos = _pending[pendingIDX++];
+                            Debug.Assert(pos.docID == doc.docID);
+                            var posDelta = pos.pos - lastPos;
                             lastPos = pos.pos;
                             
-                            int payloadLength = pos.payload == null ? 0 : pos.payload.Length;
-                            if (storePayloads)
+                            var payloadLength = pos.payload == null ? 0 : pos.payload.Length;
+                            if (_storePayloads)
                             {
                                 if (payloadLength != lastPayloadLength)
                                 {
-                                    buffer.WriteVInt((posDelta << 1) | 1);
-                                    buffer.WriteVInt(payloadLength);
+                                    _buffer.WriteVInt((posDelta << 1) | 1);
+                                    _buffer.WriteVInt(payloadLength);
                                     lastPayloadLength = payloadLength;
                                 }
                                 else
                                 {
-                                    buffer.WriteVInt(posDelta << 1);
+                                    _buffer.WriteVInt(posDelta << 1);
                                 }
                             }
                             else
                             {
-                                buffer.WriteVInt(posDelta);
+                                _buffer.WriteVInt(posDelta);
                             }
 
-                            if (indexOptions.Value.CompareTo(FieldInfo.IndexOptions_e.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS) >= 0)
+                            if (_indexOptions >= FieldInfo.IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS)
                             {
                                 //System.out.println("write=" + pos.startOffset + "," + pos.endOffset);
-                                int offsetDelta = pos.startOffset - lastOffset;
-                                int offsetLength = pos.endOffset - pos.startOffset;
+                                var offsetDelta = pos.startOffset - lastOffset;
+                                var offsetLength = pos.endOffset - pos.startOffset;
                                 if (offsetLength != lastOffsetLength)
                                 {
-                                    buffer.WriteVInt(offsetDelta << 1 | 1);
-                                    buffer.WriteVInt(offsetLength);
+                                    _buffer.WriteVInt(offsetDelta << 1 | 1);
+                                    _buffer.WriteVInt(offsetLength);
                                 }
                                 else
                                 {
-                                    buffer.WriteVInt(offsetDelta << 1);
+                                    _buffer.WriteVInt(offsetDelta << 1);
                                 }
                                 lastOffset = pos.startOffset;
                                 lastOffsetLength = offsetLength;
@@ -361,73 +359,79 @@ namespace Lucene.Net.Codecs.Pulsing
 
                             if (payloadLength > 0)
                             {
-                                Debug.Debug.Assert((storePayloads);
-                                buffer.WriteBytes(pos.payload.Bytes, 0, pos.payload.Length);
+                                Debug.Assert(_storePayloads);
+                                _buffer.WriteBytes(pos.payload.Bytes, 0, pos.payload.Length);
                             }
                         }
                     }
                 }
-                else if (indexOptions == FieldInfo.IndexOptions_e.DOCS_AND_FREQS)
+                else switch (_indexOptions)
                 {
-                    int lastDocID = 0;
-                    for (int posIDX = 0; posIDX < pendingCount; posIDX++)
+                    case FieldInfo.IndexOptions.DOCS_AND_FREQS:
                     {
-                        Position doc = pending[posIDX];
-                        int delta = doc.docID - lastDocID;
-                        Debug.Debug.Assert((doc.termFreq != 0);
-
-                        if (doc.termFreq == 1)
-                        {
-                            buffer.WriteVInt((delta << 1) | 1);
-                        }
-                        else
+                        var lastDocId = 0;
+                        for (var posIdx = 0; posIdx < _pendingCount; posIdx++)
                         {
-                            buffer.WriteVInt(delta << 1);
-                            buffer.WriteVInt(doc.termFreq);
+                            var doc = _pending[posIdx];
+                            var delta = doc.docID - lastDocId;
+
+                            Debug.Assert(doc.termFreq != 0);
+
+                            if (doc.termFreq == 1)
+                            {
+                                _buffer.WriteVInt((delta << 1) | 1);
+                            }
+                            else
+                            {
+                                _buffer.WriteVInt(delta << 1);
+                                _buffer.WriteVInt(doc.termFreq);
+                            }
+                            lastDocId = doc.docID;
                         }
-                        lastDocID = doc.docID;
                     }
-                }
-                else if (indexOptions == FieldInfo.IndexOptions_e.DOCS_ONLY)
-                {
-                    int lastDocID = 0;
-                    for (int posIDX = 0; posIDX < pendingCount; posIDX++)
+                        break;
+                    case FieldInfo.IndexOptions.DOCS_ONLY:
                     {
-                        Position doc = pending[posIDX];
-                        buffer.WriteVInt(doc.docID - lastDocID);
-                        lastDocID = doc.docID;
+                        var lastDocId = 0;
+                        for (var posIdx = 0; posIdx < _pendingCount; posIdx++)
+                        {
+                            var doc = _pending[posIdx];
+                            _buffer.WriteVInt(doc.docID - lastDocId);
+                            lastDocId = doc.docID;
+                        }
                     }
+                        break;
                 }
 
-                state.bytes = new byte[(int) buffer.FilePointer];
-                buffer.WriteTo((sbyte[])(Array)state.bytes, 0);
-                buffer.Reset();
+                state.BYTES = new byte[(int) _buffer.FilePointer];
+                _buffer.WriteTo((sbyte[])(Array)state.BYTES, 0);
+                _buffer.Reset();
             }
-            pendingCount = 0;
+            _pendingCount = 0;
         }
 
-        public override void EncodeTerm(long[] empty, DataOutput output, FieldInfo fieldInfo, BlockTermState _state,
-            bool absolute)
+        public override void EncodeTerm(long[] empty, DataOutput output, FieldInfo fieldInfo, BlockTermState state,
+            bool abs)
         {
-            PulsingTermState state = (PulsingTermState) _state;
-            Debug.Debug.Assert((empty.Length == 0);
-            this.absolute = this.absolute || absolute;
-            if (state.bytes == null)
+            var _state = (PulsingTermState) state;
+            Debug.Assert(empty.Length == 0);
+            _absolute = _absolute || abs;
+            if (_state.BYTES == null)
             {
-                _wrappedPostingsWriter.EncodeTerm(longs, buffer, fieldInfo, state.wrappedState, this.absolute);
-                for (int i = 0; i < longsSize; i++)
+                _wrappedPostingsWriter.EncodeTerm(_longs, _buffer, fieldInfo, _state.WRAPPED_STATE, _absolute);
+                for (var i = 0; i < _longsSize; i++)
                 {
-                    output.WriteVLong(longs[i]);
+                    output.WriteVLong(_longs[i]);
                 }
-                buffer.WriteTo(output);
-                buffer.Reset();
-                this.absolute = false;
+                _buffer.WriteTo(output);
+                _buffer.Reset();
+                _absolute = false;
             }
             else
             {
-                output.WriteVInt(state.bytes.Length);
-                output.WriteBytes(state.bytes, 0, state.bytes.Length);
-                this.absolute = this.absolute || absolute;
+                output.WriteVInt(_state.BYTES.Length);
+                output.WriteBytes(_state.BYTES, 0, _state.BYTES.Length);
+                _absolute = _absolute || abs;
             }
         }
 
@@ -441,16 +445,16 @@ namespace Lucene.Net.Codecs.Pulsing
                 return;
             }
 
-            String summaryFileName = IndexFileNames.SegmentFileName(segmentState.SegmentInfo.Name,
-                segmentState.SegmentSuffix, SUMMARY_EXTENSION);
+            var summaryFileName = IndexFileNames.SegmentFileName(_segmentState.SegmentInfo.Name,
+                _segmentState.SegmentSuffix, SUMMARY_EXTENSION);
             IndexOutput output = null;
             try
             {
                 output =
-                    segmentState.Directory.CreateOutput(summaryFileName, segmentState.Context);
+                    _segmentState.Directory.CreateOutput(summaryFileName, _segmentState.Context);
                 CodecUtil.WriteHeader(output, CODEC, VERSION_CURRENT);
-                output.WriteVInt(fields.Count);
-                foreach (FieldMetaData field in fields)
+                output.WriteVInt(_fields.Count);
+                foreach (var field in _fields)
                 {
                     output.WriteVInt(field.FieldNumber);
                     output.WriteVInt(field.LongsSize);
@@ -464,48 +468,43 @@ namespace Lucene.Net.Codecs.Pulsing
         }
 
         // Pushes pending positions to the wrapped codec
-        private void push()
+        private void Push()
         {
-            // if (DEBUG) System.out.println("PW now push @ " + pendingCount + " wrapped=" + wrappedPostingsWriter);
-            Debug.Debug.Assert((pendingCount == pending.Length);
+            Debug.Assert(_pendingCount == _pending.Length);
 
             _wrappedPostingsWriter.StartTerm();
 
             // Flush all buffered docs
-            if (indexOptions.Value.CompareTo(FieldInfo.IndexOptions_e.DOCS_AND_FREQS_AND_POSITIONS) >= 0)
+            if (_indexOptions >= FieldInfo.IndexOptions.DOCS_AND_FREQS_AND_POSITIONS)
             {
                 Position doc = null;
 
-                foreach(Position pos in pending)
+                foreach(var pos in _pending)
                 {
                     if (doc == null)
                     {
                         doc = pos;
-                        // if (DEBUG) System.out.println("PW: wrapped.startDoc docID=" + doc.docID + " tf=" + doc.termFreq);
                         _wrappedPostingsWriter.StartDoc(doc.docID, doc.termFreq);
                     }
                     else if (doc.docID != pos.docID)
                     {
-                        Debug.Debug.Assert((pos.docID > doc.docID);
-                        // if (DEBUG) System.out.println("PW: wrapped.finishDoc");
+                        Debug.Assert(pos.docID > doc.docID);
                         _wrappedPostingsWriter.FinishDoc();
                         doc = pos;
-                        // if (DEBUG) System.out.println("PW: wrapped.startDoc docID=" + doc.docID + " tf=" + doc.termFreq);
                         _wrappedPostingsWriter.StartDoc(doc.docID, doc.termFreq);
                     }
-                    // if (DEBUG) System.out.println("PW:   wrapped.addPos pos=" + pos.pos);
                     _wrappedPostingsWriter.AddPosition(pos.pos, pos.payload, pos.startOffset, pos.endOffset);
                 }
                 //wrappedPostingsWriter.finishDoc();
             }
             else
             {
-                foreach(Position doc in pending)
+                foreach(var doc in _pending)
                 {
-                    _wrappedPostingsWriter.StartDoc(doc.docID, indexOptions == FieldInfo.IndexOptions_e.DOCS_ONLY ? 0 : doc.termFreq);
+                    _wrappedPostingsWriter.StartDoc(doc.docID, _indexOptions == FieldInfo.IndexOptions.DOCS_ONLY ? 0 : doc.termFreq);
                 }
             }
-            pendingCount = -1;
+            _pendingCount = -1;
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/6d9a6ffa/src/Lucene.Net.Core/Codecs/PostingsConsumer.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Codecs/PostingsConsumer.cs b/src/Lucene.Net.Core/Codecs/PostingsConsumer.cs
index 8515972..65710df 100644
--- a/src/Lucene.Net.Core/Codecs/PostingsConsumer.cs
+++ b/src/Lucene.Net.Core/Codecs/PostingsConsumer.cs
@@ -63,7 +63,7 @@ namespace Lucene.Net.Codecs
         /// <code>freq</code> will be -1 when term frequencies are omitted
         /// for the field.
         /// </summary>
-        public abstract void StartDoc(int docID, int freq);
+        public abstract void StartDoc(int docId, int freq);
 
         /// <summary>
         /// Add a new position & payload, and start/end offset.  A


[06/26] lucenenet git commit: first commit of facet porting, failing tests will be fixed in next commits.

Posted by sy...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/TestMultipleIndexFields.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/TestMultipleIndexFields.cs b/src/Lucene.Net.Tests/core/Facet/TestMultipleIndexFields.cs
new file mode 100644
index 0000000..490836b
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/TestMultipleIndexFields.cs
@@ -0,0 +1,300 @@
+using System.Collections.Generic;
+using NUnit.Framework;
+
+namespace Lucene.Net.Facet
+{
+
+    /*
+     * 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 MockAnalyzer = Lucene.Net.Analysis.MockAnalyzer;
+    using MockTokenizer = Lucene.Net.Analysis.MockTokenizer;
+    using Document = Lucene.Net.Documents.Document;
+    using Field = Lucene.Net.Documents.Field;
+    using TextField = Lucene.Net.Documents.TextField;
+    using TaxonomyReader = Lucene.Net.Facet.Taxonomy.TaxonomyReader;
+    using TaxonomyWriter = Lucene.Net.Facet.Taxonomy.TaxonomyWriter;
+    using DirectoryTaxonomyReader = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyReader;
+    using DirectoryTaxonomyWriter = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter;
+    using AtomicReader = Lucene.Net.Index.AtomicReader;
+    using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext;
+    using IndexReader = Lucene.Net.Index.IndexReader;
+    using OpenMode = Lucene.Net.Index.IndexWriterConfig.OpenMode_e;
+    using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter;
+    using IndexSearcher = Lucene.Net.Search.IndexSearcher;
+    using MatchAllDocsQuery = Lucene.Net.Search.MatchAllDocsQuery;
+    using Directory = Lucene.Net.Store.Directory;
+    using IOUtils = Lucene.Net.Util.IOUtils;
+
+    public class TestMultipleIndexFields : FacetTestCase
+    {
+
+        private static readonly FacetField[] CATEGORIES = new FacetField[] { new FacetField("Author", "Mark Twain"), new FacetField("Author", "Stephen King"), new FacetField("Author", "Kurt Vonnegut"), new FacetField("Band", "Rock & Pop", "The Beatles"), new FacetField("Band", "Punk", "The Ramones"), new FacetField("Band", "Rock & Pop", "U2"), new FacetField("Band", "Rock & Pop", "REM"), new FacetField("Band", "Rock & Pop", "Dave Matthews Band"), new FacetField("Composer", "Bach") };
+
+        private FacetsConfig Config
+        {
+            get
+            {
+                FacetsConfig config = new FacetsConfig();
+                config.SetHierarchical("Band", true);
+                return config;
+            }
+        }
+
+        [Test]
+        public virtual void TestDefault()
+        {
+            Directory indexDir = NewDirectory();
+            Directory taxoDir = NewDirectory();
+
+            // create and open an index writer
+            var iw = new RandomIndexWriter(Random(), indexDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random(), MockTokenizer.WHITESPACE, false)));
+            // create and open a taxonomy writer
+            var tw = new DirectoryTaxonomyWriter(taxoDir, OpenMode.CREATE);
+            var config = Config;
+
+            seedIndex(tw, iw, config);
+
+            IndexReader ir = iw.Reader;
+            tw.Commit();
+
+            // prepare index reader and taxonomy.
+            var tr = new DirectoryTaxonomyReader(taxoDir);
+
+            // prepare searcher to search against
+            IndexSearcher searcher = NewSearcher(ir);
+
+            FacetsCollector sfc = performSearch(tr, ir, searcher);
+
+            // Obtain facets results and hand-test them
+            AssertCorrectResults(GetTaxonomyFacetCounts(tr, config, sfc));
+
+            assertOrdinalsExist("$facets", ir);
+
+            IOUtils.Close(tr, ir, iw, tw, indexDir, taxoDir);
+        }
+
+        [Test]
+        public virtual void TestCustom()
+        {
+            Directory indexDir = NewDirectory();
+            Directory taxoDir = NewDirectory();
+
+            // create and open an index writer
+            RandomIndexWriter iw = new RandomIndexWriter(Random(), indexDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random(), MockTokenizer.WHITESPACE, false)));
+            // create and open a taxonomy writer
+            TaxonomyWriter tw = new DirectoryTaxonomyWriter(taxoDir, OpenMode.CREATE);
+
+            FacetsConfig config = Config;
+            config.SetIndexFieldName("Author", "$author");
+            seedIndex(tw, iw, config);
+
+            IndexReader ir = iw.Reader;
+            tw.Commit();
+
+            // prepare index reader and taxonomy.
+            var tr = new DirectoryTaxonomyReader(taxoDir);
+
+            // prepare searcher to search against
+            IndexSearcher searcher = NewSearcher(ir);
+
+            FacetsCollector sfc = performSearch(tr, ir, searcher);
+
+            IDictionary<string, Facets> facetsMap = new Dictionary<string, Facets>();
+            facetsMap["Author"] = GetTaxonomyFacetCounts(tr, config, sfc, "$author");
+            Facets facets = new MultiFacets(facetsMap, GetTaxonomyFacetCounts(tr, config, sfc));
+
+            // Obtain facets results and hand-test them
+            AssertCorrectResults(facets);
+
+            assertOrdinalsExist("$facets", ir);
+            assertOrdinalsExist("$author", ir);
+
+            IOUtils.Close(tr, ir, iw, tw, indexDir, taxoDir);
+        }
+
+        [Test]
+        public virtual void TestTwoCustomsSameField()
+        {
+            Directory indexDir = NewDirectory();
+            Directory taxoDir = NewDirectory();
+
+            // create and open an index writer
+            RandomIndexWriter iw = new RandomIndexWriter(Random(), indexDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random(), MockTokenizer.WHITESPACE, false)));
+            // create and open a taxonomy writer
+            var tw = new DirectoryTaxonomyWriter(taxoDir, OpenMode.CREATE);
+
+            FacetsConfig config = Config;
+            config.SetIndexFieldName("Band", "$music");
+            config.SetIndexFieldName("Composer", "$music");
+            seedIndex(tw, iw, config);
+
+            IndexReader ir = iw.Reader;
+            tw.Commit();
+
+            // prepare index reader and taxonomy.
+            var tr = new DirectoryTaxonomyReader(taxoDir);
+
+            // prepare searcher to search against
+            IndexSearcher searcher = NewSearcher(ir);
+
+            FacetsCollector sfc = performSearch(tr, ir, searcher);
+
+            IDictionary<string, Facets> facetsMap = new Dictionary<string, Facets>();
+            Facets facets2 = GetTaxonomyFacetCounts(tr, config, sfc, "$music");
+            facetsMap["Band"] = facets2;
+            facetsMap["Composer"] = facets2;
+            Facets facets = new MultiFacets(facetsMap, GetTaxonomyFacetCounts(tr, config, sfc));
+
+            // Obtain facets results and hand-test them
+            AssertCorrectResults(facets);
+
+            assertOrdinalsExist("$facets", ir);
+            assertOrdinalsExist("$music", ir);
+            assertOrdinalsExist("$music", ir);
+
+            IOUtils.Close(tr, ir, iw, tw, indexDir, taxoDir);
+        }
+
+        private void assertOrdinalsExist(string field, IndexReader ir)
+        {
+            foreach (AtomicReaderContext context in ir.Leaves)
+            {
+                AtomicReader r = context.AtomicReader;
+                if (r.GetBinaryDocValues(field) != null)
+                {
+                    return; // not all segments must have this DocValues
+                }
+            }
+            Fail("no ordinals found for " + field);
+        }
+
+        [Test]
+        public virtual void TestDifferentFieldsAndText()
+        {
+            Directory indexDir = NewDirectory();
+            Directory taxoDir = NewDirectory();
+
+            // create and open an index writer
+            var iw = new RandomIndexWriter(Random(), indexDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random(), MockTokenizer.WHITESPACE, false)));
+            // create and open a taxonomy writer
+            var tw = new DirectoryTaxonomyWriter(taxoDir, OpenMode.CREATE);
+
+            FacetsConfig config = Config;
+            config.SetIndexFieldName("Band", "$bands");
+            config.SetIndexFieldName("Composer", "$composers");
+            seedIndex(tw, iw, config);
+
+            IndexReader ir = iw.Reader;
+            tw.Commit();
+
+            // prepare index reader and taxonomy.
+            var tr = new DirectoryTaxonomyReader(taxoDir);
+
+            // prepare searcher to search against
+            IndexSearcher searcher = NewSearcher(ir);
+
+            FacetsCollector sfc = performSearch(tr, ir, searcher);
+
+            IDictionary<string, Facets> facetsMap = new Dictionary<string, Facets>();
+            facetsMap["Band"] = GetTaxonomyFacetCounts(tr, config, sfc, "$bands");
+            facetsMap["Composer"] = GetTaxonomyFacetCounts(tr, config, sfc, "$composers");
+            Facets facets = new MultiFacets(facetsMap, GetTaxonomyFacetCounts(tr, config, sfc));
+
+            // Obtain facets results and hand-test them
+            AssertCorrectResults(facets);
+            assertOrdinalsExist("$facets", ir);
+            assertOrdinalsExist("$bands", ir);
+            assertOrdinalsExist("$composers", ir);
+
+            IOUtils.Close(tr, ir, iw, tw, indexDir, taxoDir);
+        }
+
+        [Test]
+        public virtual void TestSomeSameSomeDifferent()
+        {
+            Directory indexDir = NewDirectory();
+            Directory taxoDir = NewDirectory();
+
+            // create and open an index writer
+            RandomIndexWriter iw = new RandomIndexWriter(Random(), indexDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random(), MockTokenizer.WHITESPACE, false)));
+            // create and open a taxonomy writer
+            TaxonomyWriter tw = new DirectoryTaxonomyWriter(taxoDir, OpenMode.CREATE);
+
+            FacetsConfig config = Config;
+            config.SetIndexFieldName("Band", "$music");
+            config.SetIndexFieldName("Composer", "$music");
+            config.SetIndexFieldName("Author", "$literature");
+            seedIndex(tw, iw, config);
+
+            IndexReader ir = iw.Reader;
+            tw.Commit();
+
+            // prepare index reader and taxonomy.
+            var tr = new DirectoryTaxonomyReader(taxoDir);
+
+            // prepare searcher to search against
+            IndexSearcher searcher = NewSearcher(ir);
+
+            FacetsCollector sfc = performSearch(tr, ir, searcher);
+
+            IDictionary<string, Facets> facetsMap = new Dictionary<string, Facets>();
+            Facets facets2 = GetTaxonomyFacetCounts(tr, config, sfc, "$music");
+            facetsMap["Band"] = facets2;
+            facetsMap["Composer"] = facets2;
+            facetsMap["Author"] = GetTaxonomyFacetCounts(tr, config, sfc, "$literature");
+            Facets facets = new MultiFacets(facetsMap, GetTaxonomyFacetCounts(tr, config, sfc));
+
+            // Obtain facets results and hand-test them
+            AssertCorrectResults(facets);
+            assertOrdinalsExist("$music", ir);
+            assertOrdinalsExist("$literature", ir);
+
+            IOUtils.Close(tr, ir, iw, tw);
+            IOUtils.Close(indexDir, taxoDir);
+        }
+
+        
+        private void AssertCorrectResults(Facets facets)
+        {
+            Assert.AreEqual(5, facets.GetSpecificValue("Band"));
+            Assert.AreEqual("dim=Band path=[] value=5 childCount=2\n  Rock & Pop (4)\n  Punk (1)\n", facets.GetTopChildren(10, "Band").ToString());
+            Assert.AreEqual("dim=Band path=[Rock & Pop] value=4 childCount=4\n  The Beatles (1)\n  U2 (1)\n  REM (1)\n  Dave Matthews Band (1)\n", facets.GetTopChildren(10, "Band", "Rock & Pop").ToString());
+            Assert.AreEqual("dim=Author path=[] value=3 childCount=3\n  Mark Twain (1)\n  Stephen King (1)\n  Kurt Vonnegut (1)\n", facets.GetTopChildren(10, "Author").ToString());
+        }
+
+        
+        private static FacetsCollector performSearch(TaxonomyReader tr, IndexReader ir, IndexSearcher searcher)
+        {
+            FacetsCollector fc = new FacetsCollector();
+            FacetsCollector.Search(searcher, new MatchAllDocsQuery(), 10, fc);
+            return fc;
+        }
+
+        private static void seedIndex(TaxonomyWriter tw, RandomIndexWriter iw, FacetsConfig config)
+        {
+            foreach (FacetField ff in CATEGORIES)
+            {
+                Document doc = new Document();
+                doc.Add(ff);
+                doc.Add(new TextField("content", "alpha", Field.Store.YES));
+                iw.AddDocument(config.Build(tw, doc));
+            }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/TestRandomSamplingFacetsCollector.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/TestRandomSamplingFacetsCollector.cs b/src/Lucene.Net.Tests/core/Facet/TestRandomSamplingFacetsCollector.cs
new file mode 100644
index 0000000..3ada2c5
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/TestRandomSamplingFacetsCollector.cs
@@ -0,0 +1,154 @@
+using System;
+using System.Diagnostics;
+using Lucene.Net.Randomized.Generators;
+using NUnit.Framework;
+
+namespace Lucene.Net.Facet
+{
+
+    using Document = Lucene.Net.Documents.Document;
+    using Store = Lucene.Net.Documents.Field.Store;
+    using StringField = Lucene.Net.Documents.StringField;
+    using MatchingDocs = Lucene.Net.Facet.FacetsCollector.MatchingDocs;
+    using FastTaxonomyFacetCounts = Lucene.Net.Facet.Taxonomy.FastTaxonomyFacetCounts;
+    using TaxonomyReader = Lucene.Net.Facet.Taxonomy.TaxonomyReader;
+    using DirectoryTaxonomyReader = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyReader;
+    using DirectoryTaxonomyWriter = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter;
+    using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter;
+    using Term = Lucene.Net.Index.Term;
+    using IndexSearcher = Lucene.Net.Search.IndexSearcher;
+    using MultiCollector = Lucene.Net.Search.MultiCollector;
+    using TermQuery = Lucene.Net.Search.TermQuery;
+    using Directory = Lucene.Net.Store.Directory;
+    using IOUtils = Lucene.Net.Util.IOUtils;
+
+    /*
+     * 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.
+     */
+
+    public class TestRandomSamplingFacetsCollector : FacetTestCase
+    {
+
+        [Test]
+        public virtual void TestRandomSampling()
+        {
+            Directory dir = NewDirectory();
+            Directory taxoDir = NewDirectory();
+
+            DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir);
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir);
+
+            FacetsConfig config = new FacetsConfig();
+
+            int numDocs = AtLeast(10000);
+            for (int i = 0; i < numDocs; i++)
+            {
+                Document doc = new Document();
+                doc.Add(new StringField("EvenOdd", (i % 2 == 0) ? "even" : "odd", Store.NO));
+                doc.Add(new FacetField("iMod10", Convert.ToString(i % 10)));
+                writer.AddDocument(config.Build(taxoWriter, doc));
+            }
+            Random random = Random();
+
+            // NRT open
+            IndexSearcher searcher = NewSearcher(writer.Reader);
+            var taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+            IOUtils.Close(writer, taxoWriter);
+
+            // Test empty results
+            RandomSamplingFacetsCollector collectRandomZeroResults = new RandomSamplingFacetsCollector(numDocs / 10, random.NextLong());
+
+            // There should be no divisions by zero
+            searcher.Search(new TermQuery(new Term("EvenOdd", "NeverMatches")), collectRandomZeroResults);
+
+            // There should be no divisions by zero and no null result
+            Assert.NotNull(collectRandomZeroResults.GetMatchingDocs);
+
+            // There should be no results at all
+            foreach (MatchingDocs doc in collectRandomZeroResults.GetMatchingDocs)
+            {
+                Assert.AreEqual(0, doc.totalHits);
+            }
+
+            // Now start searching and retrieve results.
+
+            // Use a query to select half of the documents.
+            TermQuery query = new TermQuery(new Term("EvenOdd", "even"));
+
+            // there will be 5 facet values (0, 2, 4, 6 and 8), as only the even (i %
+            // 10) are hits.
+            // there is a REAL small chance that one of the 5 values will be missed when
+            // sampling.
+            // but is that 0.8 (chance not to take a value) ^ 2000 * 5 (any can be
+            // missing) ~ 10^-193
+            // so that is probably not going to happen.
+            int maxNumChildren = 5;
+
+            RandomSamplingFacetsCollector random100Percent = new RandomSamplingFacetsCollector(numDocs, random.NextLong()); // no sampling
+            RandomSamplingFacetsCollector random10Percent = new RandomSamplingFacetsCollector(numDocs / 10, random.NextLong()); // 10 % of total docs, 20% of the hits
+
+            FacetsCollector fc = new FacetsCollector();
+
+            searcher.Search(query, MultiCollector.Wrap(fc, random100Percent, random10Percent));
+
+            FastTaxonomyFacetCounts random10FacetCounts = new FastTaxonomyFacetCounts(taxoReader, config, random10Percent);
+            FastTaxonomyFacetCounts random100FacetCounts = new FastTaxonomyFacetCounts(taxoReader, config, random100Percent);
+            FastTaxonomyFacetCounts exactFacetCounts = new FastTaxonomyFacetCounts(taxoReader, config, fc);
+
+            FacetResult random10Result = random10Percent.AmortizeFacetCounts(random10FacetCounts.GetTopChildren(10, "iMod10"), config, searcher);
+            FacetResult random100Result = random100FacetCounts.GetTopChildren(10, "iMod10");
+            FacetResult exactResult = exactFacetCounts.GetTopChildren(10, "iMod10");
+
+            Assert.AreEqual(random100Result, exactResult);
+
+            // we should have five children, but there is a small chance we have less.
+            // (see above).
+            Assert.True(random10Result.childCount <= maxNumChildren);
+            // there should be one child at least.
+            Assert.True(random10Result.childCount >= 1);
+
+            // now calculate some statistics to determine if the sampled result is 'ok'.
+            // because random sampling is used, the results will vary each time.
+            int sum = 0;
+            foreach (LabelAndValue lav in random10Result.labelValues)
+            {
+                sum += (int)lav.value;
+            }
+            float mu = (float)sum / (float)maxNumChildren;
+
+            float variance = 0;
+            foreach (LabelAndValue lav in random10Result.labelValues)
+            {
+                variance += (float)Math.Pow((mu - (int)lav.value), 2);
+            }
+            variance = variance / maxNumChildren;
+            float sigma = (float)Math.Sqrt(variance);
+
+            // we query only half the documents and have 5 categories. The average
+            // number of docs in a category will thus be the total divided by 5*2
+            float targetMu = numDocs / (5.0f * 2.0f);
+
+            // the average should be in the range and the standard deviation should not
+            // be too great
+            Assert.True(sigma < 200);
+            Assert.True(targetMu - 3 * sigma < mu && mu < targetMu + 3 * sigma);
+
+            IOUtils.Close(searcher.IndexReader, taxoReader, dir, taxoDir);
+        }
+
+    }
+
+}
\ No newline at end of file


[12/26] lucenenet git commit: first commit of facet porting, failing tests will be fixed in next commits.

Posted by sy...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/TopOrdAndIntQueue.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/TopOrdAndIntQueue.cs b/Lucene.Net.Facet/TopOrdAndIntQueue.cs
new file mode 100644
index 0000000..63711f2
--- /dev/null
+++ b/Lucene.Net.Facet/TopOrdAndIntQueue.cs
@@ -0,0 +1,76 @@
+using Lucene.Net.Util;
+
+namespace Lucene.Net.Facet
+{
+
+    /*
+     * 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 Lucene.Net.Util;
+
+    /// <summary>
+    /// Keeps highest results, first by largest int value,
+    ///  then tie break by smallest ord. 
+    /// </summary>
+    public class TopOrdAndIntQueue : PriorityQueue<TopOrdAndIntQueue.OrdAndValue>
+    {
+
+        /// <summary>
+        /// Holds a single entry. </summary>
+        public sealed class OrdAndValue
+        {
+
+            /// <summary>
+            /// Ordinal of the entry. </summary>
+            public int ord;
+
+            /// <summary>
+            /// Value associated with the ordinal. </summary>
+            public int value;
+
+            /// <summary>
+            /// Default constructor. </summary>
+            public OrdAndValue()
+            {
+            }
+        }
+
+        /// <summary>
+        /// Sole constructor. </summary>
+        public TopOrdAndIntQueue(int topN)
+            : base(topN, false)
+        {
+        }
+
+        public override bool LessThan(OrdAndValue a, OrdAndValue b)
+        {
+            if (a.value < b.value)
+            {
+                return true;
+            }
+            else if (a.value > b.value)
+            {
+                return false;
+            }
+            else
+            {
+                return a.ord > b.ord;
+            }
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.sln
----------------------------------------------------------------------
diff --git a/Lucene.Net.sln b/Lucene.Net.sln
index 5da27b0..afc21f4 100644
--- a/Lucene.Net.sln
+++ b/Lucene.Net.sln
@@ -1,7 +1,7 @@
 
 Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 2013
-VisualStudioVersion = 12.0.30110.0
+VisualStudioVersion = 12.0.30723.0
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lucene.Net", "src\Lucene.Net.Core\Lucene.Net.csproj", "{5D4AD9BE-1FFB-41AB-9943-25737971BF57}"
 EndProject
@@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lucene.Net.TestFramework",
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lucene.Net.Queries", "src\Lucene.Net.Queries\Lucene.Net.Queries.csproj", "{69D7956C-C2CC-4708-B399-A188FEC384C4}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lucene.Net.Facet", "Lucene.Net.Facet\Lucene.Net.Facet.csproj", "{48F7884A-9454-4E88-8413-9D35992CB440}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -62,6 +64,16 @@ Global
 		{69D7956C-C2CC-4708-B399-A188FEC384C4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
 		{69D7956C-C2CC-4708-B399-A188FEC384C4}.Release|Mixed Platforms.Build.0 = Release|Any CPU
 		{69D7956C-C2CC-4708-B399-A188FEC384C4}.Release|x86.ActiveCfg = Release|Any CPU
+		{48F7884A-9454-4E88-8413-9D35992CB440}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{48F7884A-9454-4E88-8413-9D35992CB440}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{48F7884A-9454-4E88-8413-9D35992CB440}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+		{48F7884A-9454-4E88-8413-9D35992CB440}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+		{48F7884A-9454-4E88-8413-9D35992CB440}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{48F7884A-9454-4E88-8413-9D35992CB440}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{48F7884A-9454-4E88-8413-9D35992CB440}.Release|Any CPU.Build.0 = Release|Any CPU
+		{48F7884A-9454-4E88-8413-9D35992CB440}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+		{48F7884A-9454-4E88-8413-9D35992CB440}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+		{48F7884A-9454-4E88-8413-9D35992CB440}.Release|x86.ActiveCfg = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Core/Index/BinaryDocValuesFieldUpdates.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Index/BinaryDocValuesFieldUpdates.cs b/src/Lucene.Net.Core/Index/BinaryDocValuesFieldUpdates.cs
index 46ef31b..f94da6b 100644
--- a/src/Lucene.Net.Core/Index/BinaryDocValuesFieldUpdates.cs
+++ b/src/Lucene.Net.Core/Index/BinaryDocValuesFieldUpdates.cs
@@ -200,7 +200,7 @@ namespace Lucene.Net.Index
                 this.DocsWithField = docsWithField;
             }
 
-            protected internal override void Swap(int i, int j)
+            protected override void Swap(int i, int j)
             {
                 long tmpDoc = Docs.Get(j);
                 Docs.Set(j, Docs.Get(i));
@@ -233,7 +233,7 @@ namespace Lucene.Net.Index
                 }
             }
 
-            protected internal override int Compare(int i, int j)
+            protected override int Compare(int i, int j)
             {
                 int x = (int)Docs.Get(i);
                 int y = (int)Docs.Get(j);

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Core/Index/NumericDocValuesFieldUpdates.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Index/NumericDocValuesFieldUpdates.cs b/src/Lucene.Net.Core/Index/NumericDocValuesFieldUpdates.cs
index 684f62a..6878b6f 100644
--- a/src/Lucene.Net.Core/Index/NumericDocValuesFieldUpdates.cs
+++ b/src/Lucene.Net.Core/Index/NumericDocValuesFieldUpdates.cs
@@ -172,7 +172,7 @@ namespace Lucene.Net.Index
                 this.DocsWithField = docsWithField;
             }
 
-            protected internal override void Swap(int i, int j)
+            protected override void Swap(int i, int j)
             {
                 long tmpDoc = Docs.Get(j);
                 Docs.Set(j, Docs.Get(i));
@@ -201,7 +201,7 @@ namespace Lucene.Net.Index
                 }
             }
 
-            protected internal override int Compare(int i, int j)
+            protected override int Compare(int i, int j)
             {
                 int x = (int)Docs.Get(i);
                 int y = (int)Docs.Get(j);

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Core/Index/ReaderManager.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Index/ReaderManager.cs b/src/Lucene.Net.Core/Index/ReaderManager.cs
index 835ecf1..e56dab7 100644
--- a/src/Lucene.Net.Core/Index/ReaderManager.cs
+++ b/src/Lucene.Net.Core/Index/ReaderManager.cs
@@ -64,22 +64,22 @@ namespace Lucene.Net.Index
             Current = DirectoryReader.Open(dir);
         }
 
-        protected internal override void DecRef(DirectoryReader reference)
+        protected override void DecRef(DirectoryReader reference)
         {
             reference.DecRef();
         }
 
-        protected internal override DirectoryReader RefreshIfNeeded(DirectoryReader referenceToRefresh)
+        protected override DirectoryReader RefreshIfNeeded(DirectoryReader referenceToRefresh)
         {
             return DirectoryReader.OpenIfChanged(referenceToRefresh);
         }
 
-        protected internal override bool TryIncRef(DirectoryReader reference)
+        protected override bool TryIncRef(DirectoryReader reference)
         {
             return reference.TryIncRef();
         }
 
-        protected internal override int GetRefCount(DirectoryReader reference)
+        protected override int GetRefCount(DirectoryReader reference)
         {
             return reference.RefCount;
         }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Core/Lucene.Net.csproj
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Lucene.Net.csproj b/src/Lucene.Net.Core/Lucene.Net.csproj
index 58a6c8a..84ed61d 100644
--- a/src/Lucene.Net.Core/Lucene.Net.csproj
+++ b/src/Lucene.Net.Core/Lucene.Net.csproj
@@ -638,6 +638,7 @@
     <Compile Include="Support\PriorityQueue.cs" />
     <Compile Include="Support\ReentrantLock.cs" />
     <Compile Include="Support\SetExtensions.cs" />
+    <Compile Include="Support\StreamUtils.cs" />
     <Compile Include="Support\StringBuilderExtensions.cs" />
     <Compile Include="Support\StringCharSequenceWrapper.cs" />
     <Compile Include="Support\StringSupport.cs" />
@@ -675,6 +676,7 @@
     <Compile Include="Support\ThreadClass.cs" />
     <Compile Include="Support\ThreadLock.cs" />
     <Compile Include="Support\WeakDictionary.cs" />
+    <Compile Include="Util\Accountable.cs" />
     <Compile Include="Util\ArrayInPlaceMergeSorter.cs" />
     <Compile Include="Util\ArrayIntroSorter.cs" />
     <Compile Include="Util\ArrayTimSorter.cs" />

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Core/Search/CachingWrapperFilter.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Search/CachingWrapperFilter.cs b/src/Lucene.Net.Core/Search/CachingWrapperFilter.cs
index 2f2acca..3e2dcef 100644
--- a/src/Lucene.Net.Core/Search/CachingWrapperFilter.cs
+++ b/src/Lucene.Net.Core/Search/CachingWrapperFilter.cs
@@ -99,7 +99,7 @@ namespace Lucene.Net.Search
         /// <summary>
         /// Default cache implementation: uses <seealso cref="WAH8DocIdSet"/>.
         /// </summary>
-        protected internal virtual DocIdSet CacheImpl(DocIdSetIterator iterator, AtomicReader reader)
+        protected virtual DocIdSet CacheImpl(DocIdSetIterator iterator, AtomicReader reader)
         {
             var builder = new WAH8DocIdSet.Builder();
             builder.Add(iterator);

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Core/Search/Query.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Search/Query.cs b/src/Lucene.Net.Core/Search/Query.cs
index 5a32b5b..5dbfac1 100644
--- a/src/Lucene.Net.Core/Search/Query.cs
+++ b/src/Lucene.Net.Core/Search/Query.cs
@@ -138,7 +138,7 @@ namespace Lucene.Net.Search
                 return true;
             }
 
-            if (GetType() != obj.GetType())
+            if (obj != null && GetType() != obj.GetType())
                 return false;
 
             var other = obj as Query;

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Core/Search/ReferenceManager.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Search/ReferenceManager.cs b/src/Lucene.Net.Core/Search/ReferenceManager.cs
index f693d68..4af85e7 100644
--- a/src/Lucene.Net.Core/Search/ReferenceManager.cs
+++ b/src/Lucene.Net.Core/Search/ReferenceManager.cs
@@ -71,20 +71,20 @@ namespace Lucene.Net.Search
         /// Decrement reference counting on the given reference. </summary>
         /// <exception cref="IOException"> if reference decrement on the given resource failed.
         ///  </exception>
-        protected internal abstract void DecRef(G reference);
+        protected abstract void DecRef(G reference);
 
         /// <summary>
         /// Refresh the given reference if needed. Returns {@code null} if no refresh
         /// was needed, otherwise a new refreshed reference. </summary>
         /// <exception cref="AlreadyClosedException"> if the reference manager has been <seealso cref="#close() closed"/>. </exception>
         /// <exception cref="IOException"> if the refresh operation failed </exception>
-        protected internal abstract G RefreshIfNeeded(G referenceToRefresh);
+        protected abstract G RefreshIfNeeded(G referenceToRefresh);
 
         /// <summary>
         /// Try to increment reference counting on the given reference. Return true if
         /// the operation was successful. </summary>
         /// <exception cref="AlreadyClosedException"> if the reference manager has been <seealso cref="#close() closed"/>.  </exception>
-        protected internal abstract bool TryIncRef(G reference);
+        protected abstract bool TryIncRef(G reference);
 
         /// <summary>
         /// Obtain the current reference. You must match every call to acquire with one
@@ -161,7 +161,7 @@ namespace Lucene.Net.Search
         /// <summary>
         /// Returns the current reference count of the given reference.
         /// </summary>
-        protected internal abstract int GetRefCount(G reference);
+        protected abstract int GetRefCount(G reference);
 
         /// <summary>
         ///  Called after close(), so subclass can free any resources. </summary>

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Core/Search/SearcherManager.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Search/SearcherManager.cs b/src/Lucene.Net.Core/Search/SearcherManager.cs
index 8c80a43..cc74646 100644
--- a/src/Lucene.Net.Core/Search/SearcherManager.cs
+++ b/src/Lucene.Net.Core/Search/SearcherManager.cs
@@ -110,12 +110,12 @@ namespace Lucene.Net.Search
             Current = GetSearcher(searcherFactory, DirectoryReader.Open(dir));
         }
 
-        protected internal override void DecRef(IndexSearcher reference)
+        protected override void DecRef(IndexSearcher reference)
         {
             reference.IndexReader.DecRef();
         }
 
-        protected internal override IndexSearcher RefreshIfNeeded(IndexSearcher referenceToRefresh)
+        protected override IndexSearcher RefreshIfNeeded(IndexSearcher referenceToRefresh)
         {
             IndexReader r = referenceToRefresh.IndexReader;
             Debug.Assert(r is DirectoryReader, "searcher's IndexReader should be a DirectoryReader, but got " + r);
@@ -130,12 +130,12 @@ namespace Lucene.Net.Search
             }
         }
 
-        protected internal override bool TryIncRef(IndexSearcher reference)
+        protected override bool TryIncRef(IndexSearcher reference)
         {
             return reference.IndexReader.TryIncRef();
         }
 
-        protected internal override int GetRefCount(IndexSearcher reference)
+        protected override int GetRefCount(IndexSearcher reference)
         {
             return reference.IndexReader.RefCount;
         }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Core/Search/Spans/NearSpansOrdered.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Search/Spans/NearSpansOrdered.cs b/src/Lucene.Net.Core/Search/Spans/NearSpansOrdered.cs
index 76110e5..7b52ebb 100644
--- a/src/Lucene.Net.Core/Search/Spans/NearSpansOrdered.cs
+++ b/src/Lucene.Net.Core/Search/Spans/NearSpansOrdered.cs
@@ -87,12 +87,12 @@ namespace Lucene.Net.Search.Spans
                 this.OuterInstance = outerInstance;
             }
 
-            protected internal override void Swap(int i, int j)
+            protected override void Swap(int i, int j)
             {
                 ArrayUtil.Swap(OuterInstance.SubSpansByDoc, i, j);
             }
 
-            protected internal override int Compare(int i, int j)
+            protected override int Compare(int i, int j)
             {
                 return OuterInstance.SubSpansByDoc[i].Doc() - OuterInstance.SubSpansByDoc[j].Doc();
             }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Core/Support/StreamUtils.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Support/StreamUtils.cs b/src/Lucene.Net.Core/Support/StreamUtils.cs
new file mode 100644
index 0000000..75e7d15
--- /dev/null
+++ b/src/Lucene.Net.Core/Support/StreamUtils.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Lucene.Net.Support
+{
+    public static class StreamUtils
+    {
+        static readonly BinaryFormatter Formatter = new BinaryFormatter();
+
+        public static MemoryStream SerializeToStream(object o)
+        {
+            using (var stream = new MemoryStream())
+            {
+                Formatter.Serialize(stream, o);
+                return stream;
+            }
+        }
+
+        public static object DeserializeFromStream(Stream stream)
+        {
+            stream.Seek(0, SeekOrigin.Begin);
+            object o = Formatter.Deserialize(stream);
+            return o;
+        }
+
+        public static object DeserializeFromStream(BinaryReader reader)
+        {
+            var stream = reader.BaseStream;
+            stream.Seek(0, SeekOrigin.Begin);
+            object o = Formatter.Deserialize(stream);
+            return o;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Core/Util/Accountable.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Util/Accountable.cs b/src/Lucene.Net.Core/Util/Accountable.cs
new file mode 100644
index 0000000..9dfa711
--- /dev/null
+++ b/src/Lucene.Net.Core/Util/Accountable.cs
@@ -0,0 +1,36 @@
+namespace Lucene.Net.Util
+{
+
+	/*
+	 * 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.
+	 */
+
+	/// <summary>
+	/// An object whose RAM usage can be computed.
+	/// 
+	/// @lucene.internal
+	/// </summary>
+	public interface Accountable
+	{
+
+	  /// <summary>
+	  /// Return the memory usage of this object in bytes. Negative values are illegal.
+	  /// </summary>
+	  long RamBytesUsed();
+
+	}
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Core/Util/ArrayInPlaceMergeSorter.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Util/ArrayInPlaceMergeSorter.cs b/src/Lucene.Net.Core/Util/ArrayInPlaceMergeSorter.cs
index 5465e40..8f1ac0c 100644
--- a/src/Lucene.Net.Core/Util/ArrayInPlaceMergeSorter.cs
+++ b/src/Lucene.Net.Core/Util/ArrayInPlaceMergeSorter.cs
@@ -36,12 +36,12 @@ namespace Lucene.Net.Util
             this.Comparator = comparator;
         }
 
-        protected internal override int Compare(int i, int j)
+        protected override int Compare(int i, int j)
         {
             return Comparator.Compare(Arr[i], Arr[j]);
         }
 
-        protected internal override void Swap(int i, int j)
+        protected override void Swap(int i, int j)
         {
             ArrayUtil.Swap(Arr, i, j);
         }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Core/Util/ArrayIntroSorter.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Util/ArrayIntroSorter.cs b/src/Lucene.Net.Core/Util/ArrayIntroSorter.cs
index b201943..d647621 100644
--- a/src/Lucene.Net.Core/Util/ArrayIntroSorter.cs
+++ b/src/Lucene.Net.Core/Util/ArrayIntroSorter.cs
@@ -38,12 +38,12 @@ namespace Lucene.Net.Util
             Pivot_Renamed = default(T);
         }
 
-        protected internal override int Compare(int i, int j)
+        protected override int Compare(int i, int j)
         {
             return Comparator.Compare(Arr[i], Arr[j]);
         }
 
-        protected internal override void Swap(int i, int j)
+        protected override void Swap(int i, int j)
         {
             ArrayUtil.Swap(Arr, i, j);
         }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Core/Util/ArrayTimSorter.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Util/ArrayTimSorter.cs b/src/Lucene.Net.Core/Util/ArrayTimSorter.cs
index da68492..9364ff0 100644
--- a/src/Lucene.Net.Core/Util/ArrayTimSorter.cs
+++ b/src/Lucene.Net.Core/Util/ArrayTimSorter.cs
@@ -48,12 +48,12 @@ namespace Lucene.Net.Util
             }
         }
 
-        protected internal override int Compare(int i, int j)
+        protected override int Compare(int i, int j)
         {
             return Comparator.Compare(Arr[i], Arr[j]);
         }
 
-        protected internal override void Swap(int i, int j)
+        protected override void Swap(int i, int j)
         {
             ArrayUtil.Swap(Arr, i, j);
         }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Core/Util/BytesRefArray.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Util/BytesRefArray.cs b/src/Lucene.Net.Core/Util/BytesRefArray.cs
index 2a5eca1..2554c76 100644
--- a/src/Lucene.Net.Core/Util/BytesRefArray.cs
+++ b/src/Lucene.Net.Core/Util/BytesRefArray.cs
@@ -135,14 +135,14 @@ namespace Lucene.Net.Util
                 scratch2 = new BytesRef();
             }
 
-            protected internal override void Swap(int i, int j)
+            protected override void Swap(int i, int j)
             {
                 int o = OrderedEntries[i];
                 OrderedEntries[i] = OrderedEntries[j];
                 OrderedEntries[j] = o;
             }
 
-            protected internal override int Compare(int i, int j)
+            protected override int Compare(int i, int j)
             {
                 int idx1 = OrderedEntries[i], idx2 = OrderedEntries[j];
                 return Comp.Compare(OuterInstance.Get(scratch1, idx1), OuterInstance.Get(scratch2, idx2));

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Core/Util/BytesRefHash.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Util/BytesRefHash.cs b/src/Lucene.Net.Core/Util/BytesRefHash.cs
index f1e3ef6..4b762ed 100644
--- a/src/Lucene.Net.Core/Util/BytesRefHash.cs
+++ b/src/Lucene.Net.Core/Util/BytesRefHash.cs
@@ -187,14 +187,14 @@ namespace Lucene.Net.Util
                 this.Compact = compact;
             }
 
-            protected internal override void Swap(int i, int j)
+            protected override void Swap(int i, int j)
             {
                 int o = Compact[i];
                 Compact[i] = Compact[j];
                 Compact[j] = o;
             }
 
-            protected internal override int Compare(int i, int j)
+            protected override int Compare(int i, int j)
             {
                 int id1 = Compact[i], id2 = Compact[j];
                 Debug.Assert(OuterInstance.BytesStart.Length > id1 && OuterInstance.BytesStart.Length > id2);

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Core/Util/CollectionUtil.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Util/CollectionUtil.cs b/src/Lucene.Net.Core/Util/CollectionUtil.cs
index fc692fa..3de4d33 100644
--- a/src/Lucene.Net.Core/Util/CollectionUtil.cs
+++ b/src/Lucene.Net.Core/Util/CollectionUtil.cs
@@ -62,12 +62,12 @@ namespace Lucene.Net.Util
                 }
             }
 
-            protected internal override void Swap(int i, int j)
+            protected override void Swap(int i, int j)
             {
                 list = list.Swap(i, j);
             }
 
-            protected internal override int Compare(int i, int j)
+            protected override int Compare(int i, int j)
             {
                 return Comp.Compare(list[i], list[j]);
             }
@@ -104,7 +104,7 @@ namespace Lucene.Net.Util
                 }
             }
 
-            protected internal override void Swap(int i, int j)
+            protected override void Swap(int i, int j)
             {
                 List = List.Swap(i, j);
             }
@@ -127,7 +127,7 @@ namespace Lucene.Net.Util
                 List[j] = Tmp[i];
             }
 
-            protected internal override int Compare(int i, int j)
+            protected override int Compare(int i, int j)
             {
                 return Comp.Compare(List[i], List[j]);
             }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Core/Util/IOUtils.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Util/IOUtils.cs b/src/Lucene.Net.Core/Util/IOUtils.cs
index 78236e4..e23d3cd 100644
--- a/src/Lucene.Net.Core/Util/IOUtils.cs
+++ b/src/Lucene.Net.Core/Util/IOUtils.cs
@@ -268,7 +268,7 @@ namespace Lucene.Net.Util
                 }
                 else
                 {
-                    suppressedExceptions = (List<Exception>) exception.Data["SuppressedExceptions"];
+                    suppressedExceptions = (List<Exception>)exception.Data["SuppressedExceptions"];
                 }
                 suppressedExceptions.Add(suppressed);
             }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Core/Util/Sorter.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Util/Sorter.cs b/src/Lucene.Net.Core/Util/Sorter.cs
index 979dfce..2176a7b 100644
--- a/src/Lucene.Net.Core/Util/Sorter.cs
+++ b/src/Lucene.Net.Core/Util/Sorter.cs
@@ -38,11 +38,11 @@ namespace Lucene.Net.Util
         ///  The contract for the returned value is the same as
         ///  <seealso cref="Comparator#compare(Object, Object)"/>.
         /// </summary>
-        protected internal abstract int Compare(int i, int j);
+        protected abstract int Compare(int i, int j);
 
         /// <summary>
         /// Swap values at slots <code>i</code> and <code>j</code>. </summary>
-        protected internal abstract void Swap(int i, int j);
+        protected abstract void Swap(int i, int j);
 
         /// <summary>
         /// Sort the slice which starts at <code>from</code> (inclusive) and ends at

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/Lucene.Net.Tests.csproj
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/Lucene.Net.Tests.csproj b/src/Lucene.Net.Tests/Lucene.Net.Tests.csproj
index 5b57114..ad426d0 100644
--- a/src/Lucene.Net.Tests/Lucene.Net.Tests.csproj
+++ b/src/Lucene.Net.Tests/Lucene.Net.Tests.csproj
@@ -65,10 +65,18 @@
     <None Include="packages.config" />
   </ItemGroup>
   <ItemGroup>
+    <ProjectReference Include="..\..\Lucene.Net.Facet\Lucene.Net.Facet.csproj">
+      <Project>{48f7884a-9454-4e88-8413-9d35992cb440}</Project>
+      <Name>Lucene.Net.Facet</Name>
+    </ProjectReference>
     <ProjectReference Include="..\Lucene.Net.Core\Lucene.Net.csproj">
       <Project>{5d4ad9be-1ffb-41ab-9943-25737971bf57}</Project>
       <Name>Lucene.Net</Name>
     </ProjectReference>
+    <ProjectReference Include="..\Lucene.Net.Queries\Lucene.Net.Queries.csproj">
+      <Project>{69d7956c-c2cc-4708-b399-a188fec384c4}</Project>
+      <Name>Lucene.Net.Queries</Name>
+    </ProjectReference>
     <ProjectReference Include="..\Lucene.Net.TestFramework\Lucene.Net.TestFramework.csproj">
       <Project>{b2c0d749-ce34-4f62-a15e-00cb2ff5ddb3}</Project>
       <Name>Lucene.Net.TestFramework</Name>
@@ -149,6 +157,31 @@
     <Compile Include="core\Document\TestDateTools.cs" />
     <Compile Include="core\Document\TestDocument.cs" />
     <Compile Include="core\Document\TestField.cs" />
+    <Compile Include="core\Facet\AssertingSubDocsAtOnceCollector.cs" />
+    <Compile Include="core\Facet\FacetTestCase.cs" />
+    <Compile Include="core\Facet\Range\TestRangeFacetCounts.cs" />
+    <Compile Include="core\Facet\SlowRAMDirectory.cs" />
+    <Compile Include="core\Facet\SortedSet\TestSortedSetDocValuesFacets.cs" />
+    <Compile Include="core\Facet\Taxonomy\Directory\TestAddTaxonomy.cs" />
+    <Compile Include="core\Facet\Taxonomy\Directory\TestConcurrentFacetedIndexing.cs" />
+    <Compile Include="core\Facet\Taxonomy\Directory\TestDirectoryTaxonomyReader.cs" />
+    <Compile Include="core\Facet\Taxonomy\Directory\TestDirectoryTaxonomyWriter.cs" />
+    <Compile Include="core\Facet\Taxonomy\TestCachedOrdinalsReader.cs" />
+    <Compile Include="core\Facet\Taxonomy\TestFacetLabel.cs" />
+    <Compile Include="core\Facet\Taxonomy\TestLRUHashMap.cs" />
+    <Compile Include="core\Facet\Taxonomy\TestSearcherTaxonomyManager.cs" />
+    <Compile Include="core\Facet\Taxonomy\TestTaxonomyCombined.cs" />
+    <Compile Include="core\Facet\Taxonomy\TestTaxonomyFacetAssociations.cs" />
+    <Compile Include="core\Facet\Taxonomy\TestTaxonomyFacetCounts.cs" />
+    <Compile Include="core\Facet\Taxonomy\TestTaxonomyFacetCounts2.cs" />
+    <Compile Include="core\Facet\Taxonomy\TestTaxonomyFacetSumValueSource.cs" />
+    <Compile Include="core\Facet\Taxonomy\WriterCache\TestCharBlockArray.cs" />
+    <Compile Include="core\Facet\Taxonomy\WriterCache\TestCompactLabelToOrdinal.cs" />
+    <Compile Include="core\Facet\TestDrillDownQuery.cs" />
+    <Compile Include="core\Facet\TestDrillSideways.cs" />
+    <Compile Include="core\Facet\TestFacetsConfig.cs" />
+    <Compile Include="core\Facet\TestMultipleIndexFields.cs" />
+    <Compile Include="core\Facet\TestRandomSamplingFacetsCollector.cs" />
     <Compile Include="core\Index\BinaryTokenStream.cs" />
     <Compile Include="core\Index\Test2BBinaryDocValues.cs" />
     <Compile Include="core\Index\Test2BDocs.cs" />

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/AssertingSubDocsAtOnceCollector.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/AssertingSubDocsAtOnceCollector.cs b/src/Lucene.Net.Tests/core/Facet/AssertingSubDocsAtOnceCollector.cs
new file mode 100644
index 0000000..c3730df
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/AssertingSubDocsAtOnceCollector.cs
@@ -0,0 +1,85 @@
+using System.Collections.Generic;
+using Apache.NMS;
+
+namespace Lucene.Net.Facet
+{
+
+    /*
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements.  See the NOTICE file distributed with
+     * this work for additional information regarding copyright ownership.
+     * The ASF licenses this file to You under the Apache License, Version 2.0
+     * (the "License"); you may not use this file except in compliance with
+     * the License.  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     */
+
+
+    using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext;
+    using Collector = Lucene.Net.Search.Collector;
+    using ChildScorer = Lucene.Net.Search.Scorer.ChildScorer;
+    using Scorer = Lucene.Net.Search.Scorer;
+
+    /// <summary>
+    /// Verifies in collect() that all child subScorers are on
+    ///  the collected doc. 
+    /// </summary>
+    internal class AssertingSubDocsAtOnceCollector : Collector
+    {
+
+        // TODO: allow wrapping another Collector
+
+        internal IList<Scorer> allScorers;
+
+        public override Scorer Scorer
+        {
+            set
+            {
+                // Gathers all scorers, including value and "under":
+                allScorers = new List<Scorer>();
+                allScorers.Add(value);
+                int upto = 0;
+                while (upto < allScorers.Count)
+                {
+                    value = allScorers[upto++];
+                    foreach (ChildScorer sub in value.Children)
+                    {
+                        allScorers.Add(sub.Child);
+                    }
+                }
+            }
+        }
+
+        public override void Collect(int docID)
+        {
+            foreach (Scorer s in allScorers)
+            {
+                if (docID != s.DocID())
+                {
+                    throw new IllegalStateException("subScorer=" + s + " has docID=" + s.DocID() + " != collected docID=" + docID);
+                }
+            }
+        }
+
+        public override AtomicReaderContext NextReader
+        {
+            set
+            {
+            }
+        }
+
+        public override bool AcceptsDocsOutOfOrder()
+        {
+            return false;
+        }
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/FacetTestCase.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/FacetTestCase.cs b/src/Lucene.Net.Tests/core/Facet/FacetTestCase.cs
new file mode 100644
index 0000000..49d5f0f
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/FacetTestCase.cs
@@ -0,0 +1,293 @@
+using System;
+using System.Diagnostics;
+using System.Collections.Generic;
+using System.Linq;
+using Lucene.Net.Facet;
+using Lucene.Net.Randomized.Generators;
+using Lucene.Net.Support;
+using NUnit.Framework;
+
+namespace Lucene.Net.Facet
+{
+
+    /*
+     * 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 CachedOrdinalsReader = Lucene.Net.Facet.Taxonomy.CachedOrdinalsReader;
+    using DocValuesOrdinalsReader = Lucene.Net.Facet.Taxonomy.DocValuesOrdinalsReader;
+    using FastTaxonomyFacetCounts = Lucene.Net.Facet.Taxonomy.FastTaxonomyFacetCounts;
+    using OrdinalsReader = Lucene.Net.Facet.Taxonomy.OrdinalsReader;
+    using TaxonomyFacetCounts = Lucene.Net.Facet.Taxonomy.TaxonomyFacetCounts;
+    using TaxonomyReader = Lucene.Net.Facet.Taxonomy.TaxonomyReader;
+    using BytesRef = Lucene.Net.Util.BytesRef;
+    using SuppressCodecs = Lucene.Net.Util.LuceneTestCase.SuppressCodecs;
+    using LuceneTestCase = Lucene.Net.Util.LuceneTestCase;
+    using TestUtil = Lucene.Net.Util.TestUtil;
+
+    [TestFixture]
+    public abstract class FacetTestCase : LuceneTestCase
+    {
+        public virtual Facets GetTaxonomyFacetCounts(TaxonomyReader taxoReader, FacetsConfig config, FacetsCollector c)
+        {
+            return GetTaxonomyFacetCounts(taxoReader, config, c, FacetsConfig.DEFAULT_INDEX_FIELD_NAME);
+        }
+        public virtual Facets GetTaxonomyFacetCounts(TaxonomyReader taxoReader, FacetsConfig config, FacetsCollector c, string indexFieldName)
+        {
+            Facets facets;
+            if (Random().NextBoolean())
+            {
+                facets = new FastTaxonomyFacetCounts(indexFieldName, taxoReader, config, c);
+            }
+            else
+            {
+                OrdinalsReader ordsReader = new DocValuesOrdinalsReader(indexFieldName);
+                if (Random().NextBoolean())
+                {
+                    ordsReader = new CachedOrdinalsReader(ordsReader);
+                }
+                facets = new TaxonomyFacetCounts(ordsReader, taxoReader, config, c);
+            }
+
+            return facets;
+        }
+
+        protected internal virtual string[] GetRandomTokens(int count)
+        {
+            string[] tokens = new string[count];
+            for (int i = 0; i < tokens.Length; i++)
+            {
+                tokens[i] = TestUtil.RandomRealisticUnicodeString(Random(), 1, 10);
+                //tokens[i] = TestUtil.RandomSimpleString(Random(), 1, 10);
+            }
+            return tokens;
+        }
+
+        protected internal virtual string PickToken(string[] tokens)
+        {
+            for (int i = 0; i < tokens.Length; i++)
+            {
+                if (Random().NextBoolean())
+                {
+                    return tokens[i];
+                }
+            }
+
+            // Move long tail onto first token:
+            return tokens[0];
+        }
+
+        protected internal class TestDoc
+        {
+            public string content;
+            public string[] dims;
+            public float value;
+        }
+
+        protected internal virtual IList<TestDoc> GetRandomDocs(string[] tokens, int count, int numDims)
+        {
+            IList<TestDoc> docs = new List<TestDoc>();
+            for (int i = 0; i < count; i++)
+            {
+                TestDoc doc = new TestDoc();
+                docs.Add(doc);
+                doc.content = PickToken(tokens);
+                doc.dims = new string[numDims];
+                for (int j = 0; j < numDims; j++)
+                {
+                    doc.dims[j] = PickToken(tokens);
+                    if (Random().Next(10) < 3)
+                    {
+                        break;
+                    }
+                }
+                if (VERBOSE)
+                {
+                    Console.WriteLine("  doc " + i + ": content=" + doc.content);
+                    for (int j = 0; j < numDims; j++)
+                    {
+                        if (doc.dims[j] != null)
+                        {
+                            Console.WriteLine("    dim[" + j + "]=" + doc.dims[j]);
+                        }
+                    }
+                }
+            }
+
+            return docs;
+        }
+
+        protected internal virtual void SortTies(IList<FacetResult> results)
+        {
+            foreach (FacetResult result in results)
+            {
+                SortTies(result.labelValues);
+            }
+        }
+
+        protected internal virtual void SortTies(LabelAndValue[] labelValues)
+        {
+            double lastValue = -1;
+            int numInRow = 0;
+            int i = 0;
+            while (i <= labelValues.Length)
+            {
+                if (i < labelValues.Length && (double)labelValues[i].value == lastValue)
+                {
+                    numInRow++;
+                }
+                else
+                {
+                    if (numInRow > 1)
+                    {
+                        Array.Sort(labelValues, i - numInRow, i, new ComparatorAnonymousInnerClassHelper(this));
+                    }
+                    numInRow = 1;
+                    if (i < labelValues.Length)
+                    {
+                        lastValue = (double)labelValues[i].value;
+                    }
+                }
+                i++;
+            }
+        }
+
+        private class ComparatorAnonymousInnerClassHelper : IComparer<LabelAndValue>
+        {
+            private readonly FacetTestCase outerInstance;
+
+            public ComparatorAnonymousInnerClassHelper(FacetTestCase outerInstance)
+            {
+                this.outerInstance = outerInstance;
+            }
+
+            public virtual int Compare(LabelAndValue a, LabelAndValue b)
+            {
+                Debug.Assert((double)a.value == (double)b.value);
+                return (new BytesRef(a.label)).CompareTo(new BytesRef(b.label));
+            }
+        }
+
+        protected internal virtual void SortLabelValues(IList<LabelAndValue> labelValues)
+        {
+            var resArray = labelValues.ToArray();
+            Array.Sort(resArray,new ComparatorAnonymousInnerClassHelper2(this));
+            labelValues = resArray.ToList();
+        }
+
+        private class ComparatorAnonymousInnerClassHelper2 : IComparer<LabelAndValue>
+        {
+            private readonly FacetTestCase outerInstance;
+
+            public ComparatorAnonymousInnerClassHelper2(FacetTestCase outerInstance)
+            {
+                this.outerInstance = outerInstance;
+            }
+
+            public virtual int Compare(LabelAndValue a, LabelAndValue b)
+            {
+                if ((double)a.value > (double)b.value)
+                {
+                    return -1;
+                }
+                else if ((double)a.value < (double)b.value)
+                {
+                    return 1;
+                }
+                else
+                {
+                    return (new BytesRef(a.label)).CompareTo(new BytesRef(b.label));
+                }
+            }
+        }
+
+        protected internal virtual void SortFacetResults(IList<FacetResult> results)
+        {
+            var resArray = results.ToArray();
+            Array.Sort(resArray, new ComparatorAnonymousInnerClassHelper3(this));
+            results = resArray.ToList();
+        }
+
+        private class ComparatorAnonymousInnerClassHelper3 : IComparer<FacetResult>
+        {
+            private readonly FacetTestCase outerInstance;
+
+            public ComparatorAnonymousInnerClassHelper3(FacetTestCase outerInstance)
+            {
+                this.outerInstance = outerInstance;
+            }
+
+            public virtual int Compare(FacetResult a, FacetResult b)
+            {
+                if ((double)a.value > (double)b.value)
+                {
+                    return -1;
+                }
+                else if ((double)b.value > (double)a.value)
+                {
+                    return 1;
+                }
+                else
+                {
+                    return 0;
+                }
+            }
+        }
+
+        [Test]
+        protected internal virtual void AssertFloatValuesEquals(IList<FacetResult> a, IList<FacetResult> b)
+        {
+            Assert.AreEqual(a.Count, b.Count);
+            float lastValue = float.PositiveInfinity;
+            IDictionary<string, FacetResult> aByDim = new Dictionary<string, FacetResult>();
+            for (int i = 0; i < a.Count; i++)
+            {
+                Assert.True((float)a[i].value <= lastValue);
+                lastValue = (float)a[i].value;
+                aByDim[a[i].dim] = a[i];
+            }
+            lastValue = float.PositiveInfinity;
+            IDictionary<string, FacetResult> bByDim = new Dictionary<string, FacetResult>();
+            for (int i = 0; i < b.Count; i++)
+            {
+                bByDim[b[i].dim] = b[i];
+                Assert.True((float)b[i].value <= lastValue);
+                lastValue = (float)b[i].value;
+            }
+            foreach (string dim in aByDim.Keys)
+            {
+                AssertFloatValuesEquals(aByDim[dim], bByDim[dim]);
+            }
+        }
+
+        [Test]
+        protected internal virtual void AssertFloatValuesEquals(FacetResult a, FacetResult b)
+        {
+            Assert.AreEqual(a.dim, b.dim);
+            Assert.True(Arrays.Equals(a.path, b.path));
+            Assert.AreEqual(a.childCount, b.childCount);
+            Assert.AreEqual((float)a.value, (float)b.value, (float)a.value / 1e5);
+            Assert.AreEqual(a.labelValues.Length, b.labelValues.Length);
+            for (int i = 0; i < a.labelValues.Length; i++)
+            {
+                Assert.AreEqual(a.labelValues[i].label, b.labelValues[i].label);
+                Assert.AreEqual((float)a.labelValues[i].value, (float)b.labelValues[i].value, (float)a.labelValues[i].value / 1e5);
+            }
+        }
+    }
+
+}
\ No newline at end of file


[26/26] lucenenet git commit: Merge remote-tracking branch 'eladmarg/master'

Posted by sy...@apache.org.
Merge remote-tracking branch 'eladmarg/master'

Conflicts:
	src/Lucene.Net.Core/Search/Query.cs
	src/Lucene.Net.TestFramework/Index/BaseIndexFileFormatTestCase.cs


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

Branch: refs/heads/master
Commit: 31a6e4fe5369c7701ad59db293104e3a6f442b33
Parents: 712ea2e eabf7f2
Author: Itamar Syn-Hershko <it...@code972.com>
Authored: Fri Nov 14 13:56:17 2014 +0200
Committer: Itamar Syn-Hershko <it...@code972.com>
Committed: Fri Nov 14 13:56:17 2014 +0200

----------------------------------------------------------------------
 Lucene.Net.Facet/DrillDownQuery.cs              |  388 +++++
 Lucene.Net.Facet/DrillSideways.cs               |  295 ++++
 Lucene.Net.Facet/DrillSidewaysQuery.cs          |  316 +++++
 Lucene.Net.Facet/DrillSidewaysScorer.cs         |  826 +++++++++++
 Lucene.Net.Facet/FacetField.cs                  |   91 ++
 Lucene.Net.Facet/FacetResult.cs                 |  104 ++
 Lucene.Net.Facet/Facets.cs                      |   64 +
 Lucene.Net.Facet/FacetsCollector.cs             |  372 +++++
 Lucene.Net.Facet/FacetsConfig.cs                |  712 ++++++++++
 Lucene.Net.Facet/LabelAndValue.cs               |   66 +
 Lucene.Net.Facet/Lucene.Net.Facet.csproj        |  123 ++
 Lucene.Net.Facet/MultiFacets.cs                 |  103 ++
 Lucene.Net.Facet/Properties/AssemblyInfo.cs     |   36 +
 .../RandomSamplingFacetsCollector.cs            |  323 +++++
 Lucene.Net.Facet/Range/DoubleRange.cs           |  243 ++++
 .../Range/DoubleRangeFacetCounts.cs             |  160 +++
 Lucene.Net.Facet/Range/LongRange.cs             |  239 ++++
 Lucene.Net.Facet/Range/LongRangeCounter.cs      |  385 +++++
 Lucene.Net.Facet/Range/LongRangeFacetCounts.cs  |  143 ++
 Lucene.Net.Facet/Range/Range.cs                 |   90 ++
 Lucene.Net.Facet/Range/RangeFacetCounts.cs      |   99 ++
 .../DefaultSortedSetDocValuesReaderState.cs     |  158 +++
 .../SortedSet/SortedSetDocValuesFacetCounts.cs  |  350 +++++
 .../SortedSet/SortedSetDocValuesFacetField.cs   |   65 +
 .../SortedSet/SortedSetDocValuesReaderState.cs  |  103 ++
 .../Taxonomy/AssociationFacetField.cs           |   91 ++
 .../Taxonomy/CachedOrdinalsReader.cs            |  208 +++
 Lucene.Net.Facet/Taxonomy/CategoryPath.cs       |  316 +++++
 Lucene.Net.Facet/Taxonomy/Directory/Consts.cs   |   34 +
 .../Directory/DirectoryTaxonomyReader.cs        |  450 ++++++
 .../Directory/DirectoryTaxonomyWriter.cs        | 1202 ++++++++++++++++
 .../Taxonomy/Directory/TaxonomyIndexArrays.cs   |  252 ++++
 .../Taxonomy/DocValuesOrdinalsReader.cs         |  130 ++
 Lucene.Net.Facet/Taxonomy/FacetLabel.cs         |  219 +++
 .../Taxonomy/FastTaxonomyFacetCounts.cs         |  105 ++
 .../Taxonomy/FloatAssociationFacetField.cs      |   65 +
 .../Taxonomy/FloatTaxonomyFacets.cs             |  183 +++
 .../Taxonomy/IntAssociationFacetField.cs        |   75 +
 Lucene.Net.Facet/Taxonomy/IntTaxonomyFacets.cs  |  189 +++
 Lucene.Net.Facet/Taxonomy/LRUHashMap.cs         |  154 ++
 Lucene.Net.Facet/Taxonomy/OrdinalsReader.cs     |   68 +
 .../Taxonomy/ParallelTaxonomyArrays.cs          |   74 +
 Lucene.Net.Facet/Taxonomy/PrintTaxonomyStats.cs |  121 ++
 .../Taxonomy/SearcherTaxonomyManager.cs         |  179 +++
 .../Taxonomy/TaxonomyFacetCounts.cs             |   77 +
 .../TaxonomyFacetSumFloatAssociations.cs        |   98 ++
 .../Taxonomy/TaxonomyFacetSumIntAssociations.cs |   96 ++
 .../Taxonomy/TaxonomyFacetSumValueSource.cs     |  247 ++++
 Lucene.Net.Facet/Taxonomy/TaxonomyFacets.cs     |  137 ++
 Lucene.Net.Facet/Taxonomy/TaxonomyReader.cs     |  316 +++++
 Lucene.Net.Facet/Taxonomy/TaxonomyWriter.cs     |  125 ++
 .../Taxonomy/WriterCache/CategoryPathUtils.cs   |   99 ++
 .../Taxonomy/WriterCache/CharBlockArray.cs      |  234 +++
 .../WriterCache/Cl2oTaxonomyWriterCache.cs      |  123 ++
 .../Taxonomy/WriterCache/CollisionMap.cs        |  311 ++++
 .../WriterCache/CompactLabelToOrdinal.cs        |  522 +++++++
 .../Taxonomy/WriterCache/LabelToOrdinal.cs      |   83 ++
 .../WriterCache/LruTaxonomyWriterCache.cs       |  149 ++
 .../Taxonomy/WriterCache/NameHashIntCacheLRU.cs |   49 +
 .../Taxonomy/WriterCache/NameIntCacheLRU.cs     |  167 +++
 .../Taxonomy/WriterCache/TaxonomyWriterCache.cs |  105 ++
 Lucene.Net.Facet/TopOrdAndFloatQueue.cs         |   73 +
 Lucene.Net.Facet/TopOrdAndIntQueue.cs           |   76 +
 Lucene.Net.sln                                  |   14 +-
 src/Lucene.Net.Core/Analysis/AnalyzerWrapper.cs |    6 +-
 .../Index/BinaryDocValuesFieldUpdates.cs        |    4 +-
 .../Index/NumericDocValuesFieldUpdates.cs       |    4 +-
 src/Lucene.Net.Core/Index/ReaderManager.cs      |    8 +-
 src/Lucene.Net.Core/Lucene.Net.csproj           |    2 +
 .../Search/CachingWrapperFilter.cs              |    2 +-
 src/Lucene.Net.Core/Search/ReferenceManager.cs  |    8 +-
 src/Lucene.Net.Core/Search/SearcherManager.cs   |    8 +-
 .../Search/Spans/NearSpansOrdered.cs            |    4 +-
 src/Lucene.Net.Core/Support/StreamUtils.cs      |   40 +
 src/Lucene.Net.Core/Util/Accountable.cs         |   36 +
 .../Util/ArrayInPlaceMergeSorter.cs             |    4 +-
 src/Lucene.Net.Core/Util/ArrayIntroSorter.cs    |    4 +-
 src/Lucene.Net.Core/Util/ArrayTimSorter.cs      |    4 +-
 src/Lucene.Net.Core/Util/BytesRefArray.cs       |    4 +-
 src/Lucene.Net.Core/Util/BytesRefHash.cs        |    4 +-
 src/Lucene.Net.Core/Util/CollectionUtil.cs      |    8 +-
 src/Lucene.Net.Core/Util/IOUtils.cs             |    2 +-
 src/Lucene.Net.Core/Util/Sorter.cs              |    4 +-
 .../Index/BaseIndexFileFormatTestCase.cs        |    8 +-
 src/Lucene.Net.Tests/Lucene.Net.Tests.csproj    |   33 +
 .../Facet/AssertingSubDocsAtOnceCollector.cs    |   85 ++
 .../core/Facet/FacetTestCase.cs                 |  293 ++++
 .../core/Facet/Range/TestRangeFacetCounts.cs    | 1174 +++++++++++++++
 .../core/Facet/SlowRAMDirectory.cs              |  261 ++++
 .../SortedSet/TestSortedSetDocValuesFacets.cs   |  394 ++++++
 .../Facet/Taxonomy/Directory/TestAddTaxonomy.cs |  323 +++++
 .../Directory/TestConcurrentFacetedIndexing.cs  |  228 +++
 .../Directory/TestDirectoryTaxonomyReader.cs    |  624 ++++++++
 .../Directory/TestDirectoryTaxonomyWriter.cs    |  588 ++++++++
 .../Facet/Taxonomy/TestCachedOrdinalsReader.cs  |  116 ++
 .../core/Facet/Taxonomy/TestFacetLabel.cs       |  360 +++++
 .../core/Facet/Taxonomy/TestLRUHashMap.cs       |   65 +
 .../Taxonomy/TestSearcherTaxonomyManager.cs     |  398 ++++++
 .../core/Facet/Taxonomy/TestTaxonomyCombined.cs | 1185 ++++++++++++++++
 .../Taxonomy/TestTaxonomyFacetAssociations.cs   |  278 ++++
 .../Facet/Taxonomy/TestTaxonomyFacetCounts.cs   |  851 +++++++++++
 .../Facet/Taxonomy/TestTaxonomyFacetCounts2.cs  |  403 ++++++
 .../Taxonomy/TestTaxonomyFacetSumValueSource.cs |  610 ++++++++
 .../Taxonomy/WriterCache/TestCharBlockArray.cs  |  110 ++
 .../WriterCache/TestCompactLabelToOrdinal.cs    |  151 ++
 .../core/Facet/TestDrillDownQuery.cs            |  282 ++++
 .../core/Facet/TestDrillSideways.cs             | 1332 ++++++++++++++++++
 .../core/Facet/TestFacetsConfig.cs              |  131 ++
 .../core/Facet/TestMultipleIndexFields.cs       |  300 ++++
 .../Facet/TestRandomSamplingFacetsCollector.cs  |  154 ++
 110 files changed, 23920 insertions(+), 43 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucenenet/blob/31a6e4fe/src/Lucene.Net.TestFramework/Index/BaseIndexFileFormatTestCase.cs
----------------------------------------------------------------------
diff --cc src/Lucene.Net.TestFramework/Index/BaseIndexFileFormatTestCase.cs
index 95cb4e1,264d1ef..67e606c
--- a/src/Lucene.Net.TestFramework/Index/BaseIndexFileFormatTestCase.cs
+++ b/src/Lucene.Net.TestFramework/Index/BaseIndexFileFormatTestCase.cs
@@@ -64,13 -64,21 +64,13 @@@ namespace Lucene.Net.Inde
              IDictionary<string, long> bytesUsedByExtension = new Dictionary<string, long>();
              foreach (string file in d.ListAll())
              {
- 								string ext = IndexFileNames.GetExtension(file) ?? string.Empty;
 -                string ext = IndexFileNames.GetExtension(file);
 -                if (!string.IsNullOrWhiteSpace(ext))
 -                {
 -                    long previousLength = bytesUsedByExtension.ContainsKey(ext) ? bytesUsedByExtension[ext] : 0;
 -                    bytesUsedByExtension[ext] = previousLength + d.FileLength(file);
 -                }
 -            }
 -
 -            foreach (var elm in ExcludedExtensionsFromByteCounts())
 -            {
 -                if (bytesUsedByExtension.Keys.Contains(elm))
 -                {
 -                    bytesUsedByExtension.Remove(elm);
 -                }
++				string ext = IndexFileNames.GetExtension(file) ?? string.Empty;
 +                long previousLength = bytesUsedByExtension.ContainsKey(ext) ? bytesUsedByExtension[ext] : 0;
 +                bytesUsedByExtension[ext] = previousLength + d.FileLength(file);
              }
- 						foreach (string item in ExcludedExtensionsFromByteCounts()) {
- 							bytesUsedByExtension.Remove(item);							
- 						}
++			foreach (string item in ExcludedExtensionsFromByteCounts()) {
++				bytesUsedByExtension.Remove(item);							
++			}
              return bytesUsedByExtension;
          }
  


[08/26] lucenenet git commit: first commit of facet porting, failing tests will be fixed in next commits.

Posted by sy...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetCounts.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetCounts.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetCounts.cs
new file mode 100644
index 0000000..b4bb012
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetCounts.cs
@@ -0,0 +1,851 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Lucene.Net.Randomized.Generators;
+using Lucene.Net.Support;
+using NUnit.Framework;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * 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 MockAnalyzer = Lucene.Net.Analysis.MockAnalyzer;
+    using Document = Lucene.Net.Documents.Document;
+    using Field = Lucene.Net.Documents.Field;
+    using StringField = Lucene.Net.Documents.StringField;
+    using DirectoryTaxonomyReader = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyReader;
+    using DirectoryTaxonomyWriter = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter;
+    using DirectoryReader = Lucene.Net.Index.DirectoryReader;
+    using IndexWriter = Lucene.Net.Index.IndexWriter;
+    using IndexWriterConfig = Lucene.Net.Index.IndexWriterConfig;
+    using NoMergePolicy = Lucene.Net.Index.NoMergePolicy;
+    using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter;
+    using Term = Lucene.Net.Index.Term;
+    using IndexSearcher = Lucene.Net.Search.IndexSearcher;
+    using MatchAllDocsQuery = Lucene.Net.Search.MatchAllDocsQuery;
+    using Query = Lucene.Net.Search.Query;
+    using TermQuery = Lucene.Net.Search.TermQuery;
+    using DefaultSimilarity = Lucene.Net.Search.Similarities.DefaultSimilarity;
+    using PerFieldSimilarityWrapper = Lucene.Net.Search.Similarities.PerFieldSimilarityWrapper;
+    using Similarity = Lucene.Net.Search.Similarities.Similarity;
+    using Directory = Lucene.Net.Store.Directory;
+    using IOUtils = Lucene.Net.Util.IOUtils;
+    using TestUtil = Lucene.Net.Util.TestUtil;
+    [TestFixture]
+    public class TestTaxonomyFacetCounts : FacetTestCase
+    {
+
+        [Test]
+        public virtual void TestBasic()
+        {
+            Store.Directory dir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+
+            // Writes facet ords to a separate directory from the
+            // main index:
+            var taxoWriter = new DirectoryTaxonomyWriter(taxoDir, IndexWriterConfig.OpenMode_e.CREATE);
+
+            FacetsConfig config = new FacetsConfig();
+            config.SetHierarchical("Publish Date", true);
+
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir);
+
+            Document doc = new Document();
+            doc.Add(new FacetField("Author", "Bob"));
+            doc.Add(new FacetField("Publish Date", "2010", "10", "15"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            doc = new Document();
+            doc.Add(new FacetField("Author", "Lisa"));
+            doc.Add(new FacetField("Publish Date", "2010", "10", "20"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            doc = new Document();
+            doc.Add(new FacetField("Author", "Lisa"));
+            doc.Add(new FacetField("Publish Date", "2012", "1", "1"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            doc = new Document();
+            doc.Add(new FacetField("Author", "Susan"));
+            doc.Add(new FacetField("Publish Date", "2012", "1", "7"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            doc = new Document();
+            doc.Add(new FacetField("Author", "Frank"));
+            doc.Add(new FacetField("Publish Date", "1999", "5", "5"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            // NRT open
+            IndexSearcher searcher = NewSearcher(writer.Reader);
+
+            // NRT open
+            var taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+
+            // Aggregate the facet counts:
+            FacetsCollector c = new FacetsCollector();
+
+            // MatchAllDocsQuery is for "browsing" (counts facets
+            // for all non-deleted docs in the index); normally
+            // you'd use a "normal" query, and use MultiCollector to
+            // wrap collecting the "normal" hits and also facets:
+            searcher.Search(new MatchAllDocsQuery(), c);
+
+            Facets facets = new FastTaxonomyFacetCounts(taxoReader, config, c);
+
+            // Retrieve & verify results:
+            Assert.AreEqual("dim=Publish Date path=[] value=5 childCount=3\n  2010 (2)\n  2012 (2)\n  1999 (1)\n", facets.GetTopChildren(10, "Publish Date").ToString());
+            Assert.AreEqual("dim=Author path=[] value=5 childCount=4\n  Lisa (2)\n  Bob (1)\n  Susan (1)\n  Frank (1)\n", facets.GetTopChildren(10, "Author").ToString());
+
+            // Now user drills down on Publish Date/2010:
+            DrillDownQuery q2 = new DrillDownQuery(config);
+            q2.Add("Publish Date", "2010");
+            c = new FacetsCollector();
+            searcher.Search(q2, c);
+            facets = new FastTaxonomyFacetCounts(taxoReader, config, c);
+            Assert.AreEqual("dim=Author path=[] value=2 childCount=2\n  Bob (1)\n  Lisa (1)\n", facets.GetTopChildren(10, "Author").ToString());
+
+            Assert.AreEqual(1, facets.GetSpecificValue("Author", "Lisa"));
+
+            Assert.Null(facets.GetTopChildren(10, "Non exitent dim"));
+
+            // Smoke test PrintTaxonomyStats:
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            PrintTaxonomyStats.PrintStats(taxoReader, Console.Out, true);
+            string result = bos.ToString();
+            Assert.True(result.IndexOf("/Author: 4 immediate children; 5 total categories", StringComparison.Ordinal) != -1);
+            Assert.True(result.IndexOf("/Publish Date: 3 immediate children; 12 total categories", StringComparison.Ordinal) != -1);
+            // Make sure at least a few nodes of the tree came out:
+            Assert.True(result.IndexOf("  /1999", StringComparison.Ordinal) != -1);
+            Assert.True(result.IndexOf("  /2012", StringComparison.Ordinal) != -1);
+            Assert.True(result.IndexOf("      /20", StringComparison.Ordinal) != -1);
+
+            IOUtils.Close(writer, taxoWriter, searcher.IndexReader, taxoReader, taxoDir, dir);
+        }
+
+        // LUCENE-5333
+        [Test]
+        public virtual void TestSparseFacets()
+        {
+            Store.Directory dir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+
+            // Writes facet ords to a separate directory from the
+            // main index:
+            var taxoWriter = new DirectoryTaxonomyWriter(taxoDir, IndexWriterConfig.OpenMode_e.CREATE);
+
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir);
+            FacetsConfig config = new FacetsConfig();
+
+            Document doc = new Document();
+            doc.Add(new FacetField("a", "foo1"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            if (Random().NextBoolean())
+            {
+                writer.Commit();
+            }
+
+            doc = new Document();
+            doc.Add(new FacetField("a", "foo2"));
+            doc.Add(new FacetField("b", "bar1"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            if (Random().NextBoolean())
+            {
+                writer.Commit();
+            }
+
+            doc = new Document();
+            doc.Add(new FacetField("a", "foo3"));
+            doc.Add(new FacetField("b", "bar2"));
+            doc.Add(new FacetField("c", "baz1"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            // NRT open
+            IndexSearcher searcher = NewSearcher(writer.Reader);
+
+            // NRT open
+            var taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+
+            FacetsCollector c = new FacetsCollector();
+            searcher.Search(new MatchAllDocsQuery(), c);
+
+            Facets facets = GetTaxonomyFacetCounts(taxoReader, new FacetsConfig(), c);
+
+            // Ask for top 10 labels for any dims that have counts:
+            IList<FacetResult> results = facets.GetAllDims(10);
+
+            Assert.AreEqual(3, results.Count);
+            Assert.AreEqual("dim=a path=[] value=3 childCount=3\n  foo1 (1)\n  foo2 (1)\n  foo3 (1)\n", results[0].ToString());
+            Assert.AreEqual("dim=b path=[] value=2 childCount=2\n  bar1 (1)\n  bar2 (1)\n", results[1].ToString());
+            Assert.AreEqual("dim=c path=[] value=1 childCount=1\n  baz1 (1)\n", results[2].ToString());
+
+            IOUtils.Close(writer, taxoWriter, searcher.IndexReader, taxoReader, taxoDir, dir);
+        }
+
+        [Test]
+        public virtual void TestWrongIndexFieldName()
+        {
+            Store.Directory dir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+
+            // Writes facet ords to a separate directory from the
+            // main index:
+            DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir, IndexWriterConfig.OpenMode_e.CREATE);
+
+            FacetsConfig config = new FacetsConfig();
+            config.SetIndexFieldName("a", "$facets2");
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir);
+
+            Document doc = new Document();
+            doc.Add(new FacetField("a", "foo1"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            // NRT open
+            IndexSearcher searcher = NewSearcher(writer.Reader);
+
+            // NRT open
+            var taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+
+            FacetsCollector c = new FacetsCollector();
+            searcher.Search(new MatchAllDocsQuery(), c);
+
+            // Uses default $facets field:
+            Facets facets;
+            if (Random().NextBoolean())
+            {
+                facets = new FastTaxonomyFacetCounts(taxoReader, config, c);
+            }
+            else
+            {
+                OrdinalsReader ordsReader = new DocValuesOrdinalsReader();
+                if (Random().NextBoolean())
+                {
+                    ordsReader = new CachedOrdinalsReader(ordsReader);
+                }
+                facets = new TaxonomyFacetCounts(ordsReader, taxoReader, config, c);
+            }
+
+            // Ask for top 10 labels for any dims that have counts:
+            IList<FacetResult> results = facets.GetAllDims(10);
+            Assert.True(results.Count == 0);
+
+            try
+            {
+                facets.GetSpecificValue("a");
+                Fail("should have hit exc");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+
+            try
+            {
+                facets.GetTopChildren(10, "a");
+                Fail("should have hit exc");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+
+            IOUtils.Close(writer, taxoWriter, searcher.IndexReader, taxoReader, taxoDir, dir);
+        }
+
+        [Test]
+        public virtual void TestReallyNoNormsForDrillDown()
+        {
+            Store.Directory dir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+            IndexWriterConfig iwc = NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random()));
+            iwc.SetSimilarity(new PerFieldSimilarityWrapperAnonymousInnerClassHelper(this));
+            TaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir, IndexWriterConfig.OpenMode_e.CREATE);
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir, iwc);
+            FacetsConfig config = new FacetsConfig();
+
+            Document doc = new Document();
+            doc.Add(NewTextField("field", "text", Field.Store.NO));
+            doc.Add(new FacetField("a", "path"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+            IOUtils.Close(writer, taxoWriter, dir, taxoDir);
+        }
+
+        private class PerFieldSimilarityWrapperAnonymousInnerClassHelper : PerFieldSimilarityWrapper
+        {
+            private readonly TestTaxonomyFacetCounts outerInstance;
+
+            public PerFieldSimilarityWrapperAnonymousInnerClassHelper(TestTaxonomyFacetCounts outerInstance)
+            {
+                this.outerInstance = outerInstance;
+                sim = new DefaultSimilarity();
+            }
+
+            internal readonly Similarity sim;
+
+            public override Similarity Get(string name)
+            {
+                Assert.AreEqual("field", name);
+                return sim;
+            }
+        }
+
+        [Test]
+        public virtual void TestMultiValuedHierarchy()
+        {
+            Store.Directory dir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+            DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir, IndexWriterConfig.OpenMode_e.CREATE);
+            FacetsConfig config = new FacetsConfig();
+            config.SetHierarchical("a", true);
+            config.SetMultiValued("a", true);
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir);
+
+            Document doc = new Document();
+            doc.Add(NewTextField("field", "text", Field.Store.NO));
+            doc.Add(new FacetField("a", "path", "x"));
+            doc.Add(new FacetField("a", "path", "y"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            // NRT open
+            IndexSearcher searcher = NewSearcher(writer.Reader);
+
+            // NRT open
+            var taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+
+            // Aggregate the facet counts:
+            FacetsCollector c = new FacetsCollector();
+
+            // MatchAllDocsQuery is for "browsing" (counts facets
+            // for all non-deleted docs in the index); normally
+            // you'd use a "normal" query, and use MultiCollector to
+            // wrap collecting the "normal" hits and also facets:
+            searcher.Search(new MatchAllDocsQuery(), c);
+            Facets facets = GetTaxonomyFacetCounts(taxoReader, config, c);
+
+            try
+            {
+                facets.GetSpecificValue("a");
+                Fail("didn't hit expected exception");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+
+            FacetResult result = facets.GetTopChildren(10, "a");
+            Assert.AreEqual(1, result.labelValues.Length);
+            Assert.AreEqual(1, (int)result.labelValues[0].value);
+
+            IOUtils.Close(writer, taxoWriter, searcher.IndexReader, taxoReader, dir, taxoDir);
+        }
+
+        [Test]
+        public virtual void TestLabelWithDelimiter()
+        {
+            Store.Directory dir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir);
+            var taxoWriter = new DirectoryTaxonomyWriter(taxoDir, IndexWriterConfig.OpenMode_e.CREATE);
+
+            FacetsConfig config = new FacetsConfig();
+            config.SetMultiValued("dim", true);
+
+            Document doc = new Document();
+            doc.Add(NewTextField("field", "text", Field.Store.NO));
+            doc.Add(new FacetField("dim", "test\u001Fone"));
+            doc.Add(new FacetField("dim", "test\u001Etwo"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            // NRT open
+            IndexSearcher searcher = NewSearcher(writer.Reader);
+
+            // NRT open
+            var taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+
+            FacetsCollector c = new FacetsCollector();
+            searcher.Search(new MatchAllDocsQuery(), c);
+
+            Facets facets = GetTaxonomyFacetCounts(taxoReader, config, c);
+            Assert.AreEqual(1, facets.GetSpecificValue("dim", "test\u001Fone"));
+            Assert.AreEqual(1, facets.GetSpecificValue("dim", "test\u001Etwo"));
+
+            FacetResult result = facets.GetTopChildren(10, "dim");
+            Assert.AreEqual("dim=dim path=[] value=-1 childCount=2\n  test\u001Fone (1)\n  test\u001Etwo (1)\n", result.ToString());
+            IOUtils.Close(writer, taxoWriter, searcher.IndexReader, taxoReader, dir, taxoDir);
+        }
+
+        [Test]
+        public virtual void TestRequireDimCount()
+        {
+            Store.Directory dir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir);
+            DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir, IndexWriterConfig.OpenMode_e.CREATE);
+
+            FacetsConfig config = new FacetsConfig();
+            config.SetRequireDimCount("dim", true);
+
+            config.SetMultiValued("dim2", true);
+            config.SetRequireDimCount("dim2", true);
+
+            config.SetMultiValued("dim3", true);
+            config.SetHierarchical("dim3", true);
+            config.SetRequireDimCount("dim3", true);
+
+            Document doc = new Document();
+            doc.Add(NewTextField("field", "text", Field.Store.NO));
+            doc.Add(new FacetField("dim", "a"));
+            doc.Add(new FacetField("dim2", "a"));
+            doc.Add(new FacetField("dim2", "b"));
+            doc.Add(new FacetField("dim3", "a", "b"));
+            doc.Add(new FacetField("dim3", "a", "c"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            // NRT open
+            IndexSearcher searcher = NewSearcher(writer.Reader);
+
+            // NRT open
+            var taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+
+            FacetsCollector c = new FacetsCollector();
+            searcher.Search(new MatchAllDocsQuery(), c);
+
+            Facets facets = GetTaxonomyFacetCounts(taxoReader, config, c);
+            Assert.AreEqual(1, facets.GetTopChildren(10, "dim").value);
+            Assert.AreEqual(1, facets.GetTopChildren(10, "dim2").value);
+            Assert.AreEqual(1, facets.GetTopChildren(10, "dim3").value);
+            try
+            {
+                Assert.AreEqual(1, facets.GetSpecificValue("dim"));
+                Fail("didn't hit expected exception");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+            Assert.AreEqual(1, facets.GetSpecificValue("dim2"));
+            Assert.AreEqual(1, facets.GetSpecificValue("dim3"));
+            IOUtils.Close(writer, taxoWriter, searcher.IndexReader, taxoReader, dir, taxoDir);
+        }
+
+        // LUCENE-4583: make sure if we require > 32 KB for one
+        // document, we don't hit exc when using Facet42DocValuesFormat
+        [Test]
+        public virtual void TestManyFacetsInOneDocument()
+        {
+            AssumeTrue("default Codec doesn't support huge BinaryDocValues", TestUtil.FieldSupportsHugeBinaryDocValues(FacetsConfig.DEFAULT_INDEX_FIELD_NAME));
+            Store.Directory dir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+            IndexWriterConfig iwc = NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random()));
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir, iwc);
+            var taxoWriter = new DirectoryTaxonomyWriter(taxoDir, IndexWriterConfig.OpenMode_e.CREATE);
+
+            FacetsConfig config = new FacetsConfig();
+            config.SetMultiValued("dim", true);
+
+            int numLabels = TestUtil.NextInt(Random(), 40000, 100000);
+
+            Document doc = new Document();
+            doc.Add(NewTextField("field", "text", Field.Store.NO));
+            for (int i = 0; i < numLabels; i++)
+            {
+                doc.Add(new FacetField("dim", "" + i));
+            }
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            // NRT open
+            IndexSearcher searcher = NewSearcher(writer.Reader);
+
+            // NRT open
+            var taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+
+            // Aggregate the facet counts:
+            FacetsCollector c = new FacetsCollector();
+
+            // MatchAllDocsQuery is for "browsing" (counts facets
+            // for all non-deleted docs in the index); normally
+            // you'd use a "normal" query, and use MultiCollector to
+            // wrap collecting the "normal" hits and also facets:
+            searcher.Search(new MatchAllDocsQuery(), c);
+            Facets facets = GetTaxonomyFacetCounts(taxoReader, config, c);
+
+            FacetResult result = facets.GetTopChildren(int.MaxValue, "dim");
+            Assert.AreEqual(numLabels, result.labelValues.Length);
+            var allLabels = new HashSet<string>();
+            foreach (LabelAndValue labelValue in result.labelValues)
+            {
+                allLabels.Add(labelValue.label);
+                Assert.AreEqual(1, (int)labelValue.value);
+            }
+            Assert.AreEqual(numLabels, allLabels.Count);
+
+            IOUtils.Close(searcher.IndexReader, taxoWriter, writer, taxoReader, dir, taxoDir);
+        }
+
+        // Make sure we catch when app didn't declare field as
+        // hierarchical but it was:
+        [Test]
+        public virtual void TestDetectHierarchicalField()
+        {
+            Store.Directory dir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+            var taxoWriter = new DirectoryTaxonomyWriter(taxoDir, IndexWriterConfig.OpenMode_e.CREATE);
+            var writer = new RandomIndexWriter(Random(), dir);
+            FacetsConfig config = new FacetsConfig();
+
+            Document doc = new Document();
+            doc.Add(NewTextField("field", "text", Field.Store.NO));
+            doc.Add(new FacetField("a", "path", "other"));
+            try
+            {
+                config.Build(taxoWriter, doc);
+                Fail("did not hit expected exception");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+            IOUtils.Close(writer, taxoWriter, dir, taxoDir);
+        }
+
+        // Make sure we catch when app didn't declare field as
+        // multi-valued but it was:
+        [Test]
+        public virtual void TestDetectMultiValuedField()
+        {
+            Store.Directory dir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+            TaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir, IndexWriterConfig.OpenMode_e.CREATE);
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir);
+            FacetsConfig config = new FacetsConfig();
+
+            Document doc = new Document();
+            doc.Add(NewTextField("field", "text", Field.Store.NO));
+            doc.Add(new FacetField("a", "path"));
+            doc.Add(new FacetField("a", "path2"));
+            try
+            {
+                config.Build(taxoWriter, doc);
+                Fail("did not hit expected exception");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+            IOUtils.Close(writer, taxoWriter, dir, taxoDir);
+        }
+
+        [Test]
+        public virtual void TestSeparateIndexedFields()
+        {
+            Store.Directory indexDir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+
+            var taxoWriter = new DirectoryTaxonomyWriter(taxoDir);
+            IndexWriter iw = new IndexWriter(indexDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())));
+            FacetsConfig config = new FacetsConfig();
+            config.SetIndexFieldName("b", "$b");
+
+            for (int i = AtLeast(30); i > 0; --i)
+            {
+                Document doc = new Document();
+                doc.Add(new StringField("f", "v", Field.Store.NO));
+                doc.Add(new FacetField("a", "1"));
+                doc.Add(new FacetField("b", "1"));
+                iw.AddDocument(config.Build(taxoWriter, doc));
+            }
+
+            DirectoryReader r = DirectoryReader.Open(iw, true);
+            var taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+
+            FacetsCollector sfc = new FacetsCollector();
+            NewSearcher(r).Search(new MatchAllDocsQuery(), sfc);
+            Facets facets1 = GetTaxonomyFacetCounts(taxoReader, config, sfc);
+            Facets facets2 = GetTaxonomyFacetCounts(taxoReader, config, sfc, "$b");
+            Assert.AreEqual(r.MaxDoc, (int)facets1.GetTopChildren(10, "a").value);
+            Assert.AreEqual(r.MaxDoc, (int)facets2.GetTopChildren(10, "b").value);
+            IOUtils.Close(taxoWriter, iw, taxoReader, taxoDir, r, indexDir);
+        }
+
+        [Test]
+        public virtual void TestCountRoot()
+        {
+            // LUCENE-4882: FacetsAccumulator threw NPE if a FacetRequest was defined on CP.EMPTY
+            Store.Directory indexDir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+
+            var taxoWriter = new DirectoryTaxonomyWriter(taxoDir);
+            IndexWriter iw = new IndexWriter(indexDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())));
+            FacetsConfig config = new FacetsConfig();
+            for (int i = AtLeast(30); i > 0; --i)
+            {
+                Document doc = new Document();
+                doc.Add(new FacetField("a", "1"));
+                doc.Add(new FacetField("b", "1"));
+                iw.AddDocument(config.Build(taxoWriter, doc));
+            }
+
+            DirectoryReader r = DirectoryReader.Open(iw, true);
+            DirectoryTaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+
+            FacetsCollector sfc = new FacetsCollector();
+            NewSearcher(r).Search(new MatchAllDocsQuery(), sfc);
+            Facets facets = GetTaxonomyFacetCounts(taxoReader, config, sfc);
+            foreach (FacetResult result in facets.GetAllDims(10))
+            {
+                Assert.AreEqual(r.NumDocs, (int)result.value);
+            }
+
+            IOUtils.Close(taxoWriter, iw, taxoReader, taxoDir, r, indexDir);
+        }
+
+        [Test]
+        public virtual void TestGetFacetResultsTwice()
+        {
+            // LUCENE-4893: counts were multiplied as many times as getFacetResults was called.
+            Store.Directory indexDir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+
+            var taxoWriter = new DirectoryTaxonomyWriter(taxoDir);
+            IndexWriter iw = new IndexWriter(indexDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())));
+            FacetsConfig config = new FacetsConfig();
+
+            Document doc = new Document();
+            doc.Add(new FacetField("a", "1"));
+            doc.Add(new FacetField("b", "1"));
+            iw.AddDocument(config.Build(taxoWriter, doc));
+
+            DirectoryReader r = DirectoryReader.Open(iw, true);
+            var taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+
+            FacetsCollector sfc = new FacetsCollector();
+            NewSearcher(r).Search(new MatchAllDocsQuery(), sfc);
+
+            Facets facets = GetTaxonomyFacetCounts(taxoReader, config, sfc);
+            IList<FacetResult> res1 = facets.GetAllDims(10);
+            IList<FacetResult> res2 = facets.GetAllDims(10);
+            Assert.AreEqual(res1, res2, "calling getFacetResults twice should return the .equals()=true result");
+
+            IOUtils.Close(taxoWriter, iw, taxoReader, taxoDir, r, indexDir);
+        }
+
+        [Test]
+        public virtual void TestChildCount()
+        {
+            // LUCENE-4885: FacetResult.numValidDescendants was not set properly by FacetsAccumulator
+            var indexDir = NewDirectory();
+            var taxoDir = NewDirectory();
+
+            var taxoWriter = new DirectoryTaxonomyWriter(taxoDir);
+            IndexWriter iw = new IndexWriter(indexDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())));
+            FacetsConfig config = new FacetsConfig();
+            for (int i = 0; i < 10; i++)
+            {
+                Document doc = new Document();
+                doc.Add(new FacetField("a", Convert.ToString(i)));
+                iw.AddDocument(config.Build(taxoWriter, doc));
+            }
+
+            DirectoryReader r = DirectoryReader.Open(iw, true);
+            DirectoryTaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+
+            FacetsCollector sfc = new FacetsCollector();
+            NewSearcher(r).Search(new MatchAllDocsQuery(), sfc);
+            Facets facets = GetTaxonomyFacetCounts(taxoReader, config, sfc);
+
+            Assert.AreEqual(10, facets.GetTopChildren(2, "a").childCount);
+
+            IOUtils.Close(taxoWriter, iw, taxoReader, taxoDir, r, indexDir);
+        }
+
+        private void indexTwoDocs(TaxonomyWriter taxoWriter, IndexWriter indexWriter, FacetsConfig config, bool withContent)
+        {
+            for (int i = 0; i < 2; i++)
+            {
+                Document doc = new Document();
+                if (withContent)
+                {
+                    doc.Add(new StringField("f", "a", Field.Store.NO));
+                }
+                if (config != null)
+                {
+                    doc.Add(new FacetField("A", Convert.ToString(i)));
+                    indexWriter.AddDocument(config.Build(taxoWriter, doc));
+                }
+                else
+                {
+                    indexWriter.AddDocument(doc);
+                }
+            }
+
+            indexWriter.Commit();
+        }
+
+        [Test]
+        public virtual void TestSegmentsWithoutCategoriesOrResults()
+        {
+            // tests the accumulator when there are segments with no results
+            var indexDir = NewDirectory();
+            var taxoDir = NewDirectory();
+
+            IndexWriterConfig iwc = NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random()));
+            //iwc.MergePolicy = NoMergePolicy.INSTANCE; // prevent merges
+            IndexWriter indexWriter = new IndexWriter(indexDir, iwc);
+
+            var taxoWriter = new DirectoryTaxonomyWriter(taxoDir);
+            FacetsConfig config = new FacetsConfig();
+            indexTwoDocs(taxoWriter, indexWriter, config, false); // 1st segment, no content, with categories
+            indexTwoDocs(taxoWriter, indexWriter, null, true); // 2nd segment, with content, no categories
+            indexTwoDocs(taxoWriter, indexWriter, config, true); // 3rd segment ok
+            indexTwoDocs(taxoWriter, indexWriter, null, false); // 4th segment, no content, or categories
+            indexTwoDocs(taxoWriter, indexWriter, null, true); // 5th segment, with content, no categories
+            indexTwoDocs(taxoWriter, indexWriter, config, true); // 6th segment, with content, with categories
+            indexTwoDocs(taxoWriter, indexWriter, null, true); // 7th segment, with content, no categories
+            IOUtils.Close(indexWriter, taxoWriter);
+
+            DirectoryReader indexReader = DirectoryReader.Open(indexDir);
+            var taxoReader = new DirectoryTaxonomyReader(taxoDir);
+            IndexSearcher indexSearcher = NewSearcher(indexReader);
+
+            // search for "f:a", only segments 1 and 3 should match results
+            Query q = new TermQuery(new Term("f", "a"));
+            FacetsCollector sfc = new FacetsCollector();
+            indexSearcher.Search(q, sfc);
+            Facets facets = GetTaxonomyFacetCounts(taxoReader, config, sfc);
+            FacetResult result = facets.GetTopChildren(10, "A");
+            Assert.AreEqual(2, result.labelValues.Length, "wrong number of children");
+            foreach (LabelAndValue labelValue in result.labelValues)
+            {
+                Assert.AreEqual(2, (int)labelValue.value, "wrong weight for child " + labelValue.label);
+            }
+
+            IOUtils.Close(indexReader, taxoReader, indexDir, taxoDir);
+        }
+
+        [Test]
+        public virtual void TestRandom()
+        {
+            string[] tokens = GetRandomTokens(10);
+            Store.Directory indexDir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+
+            RandomIndexWriter w = new RandomIndexWriter(Random(), indexDir);
+            var tw = new DirectoryTaxonomyWriter(taxoDir);
+            FacetsConfig config = new FacetsConfig();
+            int numDocs = AtLeast(1000);
+            int numDims = TestUtil.NextInt(Random(), 1, 7);
+            IList<TestDoc> testDocs = GetRandomDocs(tokens, numDocs, numDims);
+            foreach (TestDoc testDoc in testDocs)
+            {
+                Document doc = new Document();
+                doc.Add(NewStringField("content", testDoc.content, Field.Store.NO));
+                for (int j = 0; j < numDims; j++)
+                {
+                    if (testDoc.dims[j] != null)
+                    {
+                        doc.Add(new FacetField("dim" + j, testDoc.dims[j]));
+                    }
+                }
+                w.AddDocument(config.Build(tw, doc));
+            }
+
+            // NRT open
+            IndexSearcher searcher = NewSearcher(w.Reader);
+
+            // NRT open
+            var tr = new DirectoryTaxonomyReader(tw);
+
+            int iters = AtLeast(100);
+            for (int iter = 0; iter < iters; iter++)
+            {
+                string searchToken = tokens[Random().Next(tokens.Length)];
+                if (VERBOSE)
+                {
+                    Console.WriteLine("\nTEST: iter content=" + searchToken);
+                }
+                FacetsCollector fc = new FacetsCollector();
+                FacetsCollector.Search(searcher, new TermQuery(new Term("content", searchToken)), 10, fc);
+                Facets facets = GetTaxonomyFacetCounts(tr, config, fc);
+
+                // Slow, yet hopefully bug-free, faceting:
+                var expectedCounts = new List<Dictionary<string, int?>>();
+                for (int i = 0; i < numDims; i++)
+                {
+                    expectedCounts[i] = new Dictionary<string, int?>();
+                }
+
+                foreach (TestDoc doc in testDocs)
+                {
+                    if (doc.content.Equals(searchToken))
+                    {
+                        for (int j = 0; j < numDims; j++)
+                        {
+                            if (doc.dims[j] != null)
+                            {
+                                int? v = expectedCounts[j][doc.dims[j]];
+                                if (v == null)
+                                {
+                                    expectedCounts[j][doc.dims[j]] = 1;
+                                }
+                                else
+                                {
+                                    expectedCounts[j][doc.dims[j]] = (int)v + 1;
+                                }
+                            }
+                        }
+                    }
+                }
+
+                IList<FacetResult> expected = new List<FacetResult>();
+                for (int i = 0; i < numDims; i++)
+                {
+                    IList<LabelAndValue> labelValues = new List<LabelAndValue>();
+                    int totCount = 0;
+                    foreach (KeyValuePair<string, int?> ent in expectedCounts[i])
+                    {
+                        labelValues.Add(new LabelAndValue(ent.Key, ent.Value.Value));
+                        totCount += ent.Value.Value;
+                    }
+                    SortLabelValues(labelValues);
+                    if (totCount > 0)
+                    {
+                        expected.Add(new FacetResult("dim" + i, new string[0], totCount, labelValues.ToArray(), labelValues.Count));
+                    }
+                }
+
+                // Sort by highest value, tie break by value:
+                SortFacetResults(expected);
+
+                IList<FacetResult> actual = facets.GetAllDims(10);
+
+                // Messy: fixup ties
+                SortTies(actual);
+
+                Assert.AreEqual(expected, actual);
+            }
+
+            IOUtils.Close(w, tw, searcher.IndexReader, tr, indexDir, taxoDir);
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetCounts2.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetCounts2.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetCounts2.cs
new file mode 100644
index 0000000..f461dde
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetCounts2.cs
@@ -0,0 +1,403 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Lucene.Net.Randomized.Generators;
+using Lucene.Net.Support;
+using NUnit.Framework;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * 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 MockAnalyzer = Lucene.Net.Analysis.MockAnalyzer;
+    using Document = Lucene.Net.Documents.Document;
+    using Store = Lucene.Net.Documents.Field.Store;
+    using StringField = Lucene.Net.Documents.StringField;
+    using DirectoryTaxonomyReader = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyReader;
+    using DirectoryTaxonomyWriter = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter;
+    using DirectoryReader = Lucene.Net.Index.DirectoryReader;
+    using IndexWriter = Lucene.Net.Index.IndexWriter;
+    using IndexWriterConfig = Lucene.Net.Index.IndexWriterConfig;
+    using NoMergePolicy = Lucene.Net.Index.NoMergePolicy;
+    using Term = Lucene.Net.Index.Term;
+    using IndexSearcher = Lucene.Net.Search.IndexSearcher;
+    using MatchAllDocsQuery = Lucene.Net.Search.MatchAllDocsQuery;
+    using TermQuery = Lucene.Net.Search.TermQuery;
+    using Directory = Lucene.Net.Store.Directory;
+    using IOUtils = Lucene.Net.Util.IOUtils;
+    [TestFixture]
+    public class TestTaxonomyFacetCounts2 : FacetTestCase
+    {
+
+        private static readonly Term A = new Term("f", "a");
+        private const string CP_A = "A", CP_B = "B";
+        private const string CP_C = "C", CP_D = "D"; // indexed w/ NO_PARENTS
+        private const int NUM_CHILDREN_CP_A = 5, NUM_CHILDREN_CP_B = 3;
+        private const int NUM_CHILDREN_CP_C = 5, NUM_CHILDREN_CP_D = 5;
+        private static readonly FacetField[] CATEGORIES_A, CATEGORIES_B;
+        private static readonly FacetField[] CATEGORIES_C, CATEGORIES_D;
+        static TestTaxonomyFacetCounts2()
+        {
+            CATEGORIES_A = new FacetField[NUM_CHILDREN_CP_A];
+            for (int i = 0; i < NUM_CHILDREN_CP_A; i++)
+            {
+                CATEGORIES_A[i] = new FacetField(CP_A, Convert.ToString(i));
+            }
+            CATEGORIES_B = new FacetField[NUM_CHILDREN_CP_B];
+            for (int i = 0; i < NUM_CHILDREN_CP_B; i++)
+            {
+                CATEGORIES_B[i] = new FacetField(CP_B, Convert.ToString(i));
+            }
+
+            // NO_PARENTS categories
+            CATEGORIES_C = new FacetField[NUM_CHILDREN_CP_C];
+            for (int i = 0; i < NUM_CHILDREN_CP_C; i++)
+            {
+                CATEGORIES_C[i] = new FacetField(CP_C, Convert.ToString(i));
+            }
+
+            // Multi-level categories
+            CATEGORIES_D = new FacetField[NUM_CHILDREN_CP_D];
+            for (int i = 0; i < NUM_CHILDREN_CP_D; i++)
+            {
+                string val = Convert.ToString(i);
+                CATEGORIES_D[i] = new FacetField(CP_D, val, val + val); // e.g. D/1/11, D/2/22...
+            }
+        }
+
+        private static Net.Store.Directory indexDir, taxoDir;
+        private static IDictionary<string, int?> allExpectedCounts, termExpectedCounts;
+
+        [TestFixtureTearDown]
+        public static void AfterClassCountingFacetsAggregatorTest()
+        {
+            IOUtils.Close(indexDir, taxoDir);
+        }
+
+        private static IList<FacetField> RandomCategories(Random random)
+        {
+            // add random categories from the two dimensions, ensuring that the same
+            // category is not added twice.
+            int numFacetsA = random.Next(3) + 1; // 1-3
+            int numFacetsB = random.Next(2) + 1; // 1-2
+            List<FacetField> categories_a = new List<FacetField>();
+            categories_a.AddRange(Arrays.AsList(CATEGORIES_A));
+            List<FacetField> categories_b = new List<FacetField>();
+            categories_b.AddRange(Arrays.AsList(CATEGORIES_B));
+            categories_a = CollectionsHelper.Shuffle(categories_a).ToList();
+            categories_b = CollectionsHelper.Shuffle(categories_b).ToList();
+
+            List<FacetField> categories = new List<FacetField>();
+            categories.AddRange(categories_a.SubList(0, numFacetsA));
+            categories.AddRange(categories_b.SubList(0, numFacetsB));
+
+            // add the NO_PARENT categories
+            categories.Add(CATEGORIES_C[Random().Next(NUM_CHILDREN_CP_C)]);
+            categories.Add(CATEGORIES_D[Random().Next(NUM_CHILDREN_CP_D)]);
+
+            return categories;
+        }
+
+        private static void AddField(Document doc)
+        {
+            doc.Add(new StringField(A.Field(), A.Text(), Store.NO));
+        }
+
+        private static void AddFacets(Document doc, FacetsConfig config, bool updateTermExpectedCounts)
+        {
+            IList<FacetField> docCategories = RandomCategories(Random());
+            foreach (FacetField ff in docCategories)
+            {
+                doc.Add(ff);
+                string cp = ff.dim + "/" + ff.path[0];
+                allExpectedCounts[cp] = allExpectedCounts[cp] + 1;
+                if (updateTermExpectedCounts)
+                {
+                    termExpectedCounts[cp] = termExpectedCounts[cp] + 1;
+                }
+            }
+            // add 1 to each NO_PARENTS dimension
+            allExpectedCounts[CP_B] = allExpectedCounts[CP_B] + 1;
+            allExpectedCounts[CP_C] = allExpectedCounts[CP_C] + 1;
+            allExpectedCounts[CP_D] = allExpectedCounts[CP_D] + 1;
+            if (updateTermExpectedCounts)
+            {
+                termExpectedCounts[CP_B] = termExpectedCounts[CP_B] + 1;
+                termExpectedCounts[CP_C] = termExpectedCounts[CP_C] + 1;
+                termExpectedCounts[CP_D] = termExpectedCounts[CP_D] + 1;
+            }
+        }
+
+        private static FacetsConfig Config
+        {
+            get
+            {
+                FacetsConfig config = new FacetsConfig();
+                config.SetMultiValued("A", true);
+                config.SetMultiValued("B", true);
+                config.SetRequireDimCount("B", true);
+                config.SetHierarchical("D", true);
+                return config;
+            }
+        }
+
+        private static void IndexDocsNoFacets(IndexWriter indexWriter)
+        {
+            int numDocs = AtLeast(2);
+            for (int i = 0; i < numDocs; i++)
+            {
+                Document doc = new Document();
+                AddField(doc);
+                indexWriter.AddDocument(doc);
+            }
+            indexWriter.Commit(); // flush a segment
+        }
+
+        private static void IndexDocsWithFacetsNoTerms(IndexWriter indexWriter, TaxonomyWriter taxoWriter, IDictionary<string, int?> expectedCounts)
+        {
+            Random random = Random();
+            int numDocs = AtLeast(random, 2);
+            FacetsConfig config = Config;
+            for (int i = 0; i < numDocs; i++)
+            {
+                Document doc = new Document();
+                AddFacets(doc, config, false);
+                indexWriter.AddDocument(config.Build(taxoWriter, doc));
+            }
+            indexWriter.Commit(); // flush a segment
+        }
+
+        private static void IndexDocsWithFacetsAndTerms(IndexWriter indexWriter, TaxonomyWriter taxoWriter, IDictionary<string, int?> expectedCounts)
+        {
+            Random random = Random();
+            int numDocs = AtLeast(random, 2);
+            FacetsConfig config = Config;
+            for (int i = 0; i < numDocs; i++)
+            {
+                Document doc = new Document();
+                AddFacets(doc, config, true);
+                AddField(doc);
+                indexWriter.AddDocument(config.Build(taxoWriter, doc));
+            }
+            indexWriter.Commit(); // flush a segment
+        }
+
+        private static void IndexDocsWithFacetsAndSomeTerms(IndexWriter indexWriter, TaxonomyWriter taxoWriter, IDictionary<string, int?> expectedCounts)
+        {
+            Random random = Random();
+            int numDocs = AtLeast(random, 2);
+            FacetsConfig config = Config;
+            for (int i = 0; i < numDocs; i++)
+            {
+                Document doc = new Document();
+                bool hasContent = random.NextBoolean();
+                if (hasContent)
+                {
+                    AddField(doc);
+                }
+                AddFacets(doc, config, hasContent);
+                indexWriter.AddDocument(config.Build(taxoWriter, doc));
+            }
+            indexWriter.Commit(); // flush a segment
+        }
+
+        // initialize expectedCounts w/ 0 for all categories
+        private static IDictionary<string, int?> newCounts()
+        {
+            IDictionary<string, int?> counts = new Dictionary<string, int?>();
+            counts[CP_A] = 0;
+            counts[CP_B] = 0;
+            counts[CP_C] = 0;
+            counts[CP_D] = 0;
+            foreach (FacetField ff in CATEGORIES_A)
+            {
+                counts[ff.dim + "/" + ff.path[0]] = 0;
+            }
+            foreach (FacetField ff in CATEGORIES_B)
+            {
+                counts[ff.dim + "/" + ff.path[0]] = 0;
+            }
+            foreach (FacetField ff in CATEGORIES_C)
+            {
+                counts[ff.dim + "/" + ff.path[0]] = 0;
+            }
+            foreach (FacetField ff in CATEGORIES_D)
+            {
+                counts[ff.dim + "/" + ff.path[0]] = 0;
+            }
+            return counts;
+        }
+
+        [TestFixtureSetUp]
+        public static void BeforeClassCountingFacetsAggregatorTest()
+        {
+            indexDir = NewDirectory();
+            taxoDir = NewDirectory();
+
+            // create an index which has:
+            // 1. Segment with no categories, but matching results
+            // 2. Segment w/ categories, but no results
+            // 3. Segment w/ categories and results
+            // 4. Segment w/ categories, but only some results
+
+            IndexWriterConfig conf = NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random()));
+            //conf.MergePolicy = NoMergePolicy.INSTANCE; // prevent merges, so we can control the index segments
+            IndexWriter indexWriter = new IndexWriter(indexDir, conf);
+            TaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir);
+
+            allExpectedCounts = newCounts();
+            termExpectedCounts = newCounts();
+
+            // segment w/ no categories
+            IndexDocsNoFacets(indexWriter);
+
+            // segment w/ categories, no content
+            IndexDocsWithFacetsNoTerms(indexWriter, taxoWriter, allExpectedCounts);
+
+            // segment w/ categories and content
+            IndexDocsWithFacetsAndTerms(indexWriter, taxoWriter, allExpectedCounts);
+
+            // segment w/ categories and some content
+            IndexDocsWithFacetsAndSomeTerms(indexWriter, taxoWriter, allExpectedCounts);
+
+            IOUtils.Close(indexWriter, taxoWriter);
+        }
+
+        [Test]
+        public virtual void TestDifferentNumResults()
+        {
+            // test the collector w/ FacetRequests and different numResults
+            DirectoryReader indexReader = DirectoryReader.Open(indexDir);
+            var taxoReader = new DirectoryTaxonomyReader(taxoDir);
+            IndexSearcher searcher = NewSearcher(indexReader);
+
+            FacetsCollector sfc = new FacetsCollector();
+            TermQuery q = new TermQuery(A);
+            searcher.Search(q, sfc);
+            Facets facets = GetTaxonomyFacetCounts(taxoReader, Config, sfc);
+            FacetResult result = facets.GetTopChildren(NUM_CHILDREN_CP_A, CP_A);
+            Assert.AreEqual(-1, (int)result.value);
+            foreach (LabelAndValue labelValue in result.labelValues)
+            {
+                Assert.AreEqual(termExpectedCounts[CP_A + "/" + labelValue.label], labelValue.value);
+            }
+            result = facets.GetTopChildren(NUM_CHILDREN_CP_B, CP_B);
+            Assert.AreEqual(termExpectedCounts[CP_B], result.value);
+            foreach (LabelAndValue labelValue in result.labelValues)
+            {
+                Assert.AreEqual(termExpectedCounts[CP_B + "/" + labelValue.label], labelValue.value);
+            }
+
+            IOUtils.Close(indexReader, taxoReader);
+        }
+
+        [Test]
+        public virtual void TestAllCounts()
+        {
+            DirectoryReader indexReader = DirectoryReader.Open(indexDir);
+            var taxoReader = new DirectoryTaxonomyReader(taxoDir);
+            IndexSearcher searcher = NewSearcher(indexReader);
+
+            FacetsCollector sfc = new FacetsCollector();
+            searcher.Search(new MatchAllDocsQuery(), sfc);
+
+            Facets facets = GetTaxonomyFacetCounts(taxoReader, Config, sfc);
+
+            FacetResult result = facets.GetTopChildren(NUM_CHILDREN_CP_A, CP_A);
+            Assert.AreEqual(-1, (int)result.value);
+            int prevValue = int.MaxValue;
+            foreach (LabelAndValue labelValue in result.labelValues)
+            {
+                Assert.AreEqual(allExpectedCounts[CP_A + "/" + labelValue.label], labelValue.value);
+                Assert.True((int)labelValue.value <= prevValue, "wrong sort order of sub results: labelValue.value=" + labelValue.value + " prevValue=" + prevValue);
+                prevValue = (int)labelValue.value;
+            }
+
+            result = facets.GetTopChildren(NUM_CHILDREN_CP_B, CP_B);
+            Assert.AreEqual(allExpectedCounts[CP_B], result.value);
+            prevValue = int.MaxValue;
+            foreach (LabelAndValue labelValue in result.labelValues)
+            {
+                Assert.AreEqual(allExpectedCounts[CP_B + "/" + labelValue.label], labelValue.value);
+                Assert.True((int)labelValue.value <= prevValue, "wrong sort order of sub results: labelValue.value=" + labelValue.value + " prevValue=" + prevValue);
+                prevValue = (int)labelValue.value;
+            }
+
+            IOUtils.Close(indexReader, taxoReader);
+        }
+
+        [Test]
+        public virtual void TestBigNumResults()
+        {
+            DirectoryReader indexReader = DirectoryReader.Open(indexDir);
+            var taxoReader = new DirectoryTaxonomyReader(taxoDir);
+            IndexSearcher searcher = NewSearcher(indexReader);
+
+            FacetsCollector sfc = new FacetsCollector();
+            searcher.Search(new MatchAllDocsQuery(), sfc);
+
+            Facets facets = GetTaxonomyFacetCounts(taxoReader, Config, sfc);
+
+            FacetResult result = facets.GetTopChildren(int.MaxValue, CP_A);
+            Assert.AreEqual(-1, (int)result.value);
+            foreach (LabelAndValue labelValue in result.labelValues)
+            {
+                Assert.AreEqual(allExpectedCounts[CP_A + "/" + labelValue.label], labelValue.value);
+            }
+            result = facets.GetTopChildren(int.MaxValue, CP_B);
+            Assert.AreEqual(allExpectedCounts[CP_B], result.value);
+            foreach (LabelAndValue labelValue in result.labelValues)
+            {
+                Assert.AreEqual(allExpectedCounts[CP_B + "/" + labelValue.label], labelValue.value);
+            }
+
+            IOUtils.Close(indexReader, taxoReader);
+        }
+
+        [Test]
+        public virtual void TestNoParents()
+        {
+            DirectoryReader indexReader = DirectoryReader.Open(indexDir);
+            var taxoReader = new DirectoryTaxonomyReader(taxoDir);
+            IndexSearcher searcher = NewSearcher(indexReader);
+
+            var sfc = new FacetsCollector();
+            searcher.Search(new MatchAllDocsQuery(), sfc);
+
+            Facets facets = GetTaxonomyFacetCounts(taxoReader, Config, sfc);
+
+            FacetResult result = facets.GetTopChildren(NUM_CHILDREN_CP_C, CP_C);
+            Assert.AreEqual(allExpectedCounts[CP_C], result.value);
+            foreach (LabelAndValue labelValue in result.labelValues)
+            {
+                Assert.AreEqual(allExpectedCounts[CP_C + "/" + labelValue.label], labelValue.value);
+            }
+            result = facets.GetTopChildren(NUM_CHILDREN_CP_D, CP_D);
+            Assert.AreEqual(allExpectedCounts[CP_C], result.value);
+            foreach (LabelAndValue labelValue in result.labelValues)
+            {
+                Assert.AreEqual(allExpectedCounts[CP_D + "/" + labelValue.label], labelValue.value);
+            }
+
+            IOUtils.Close(indexReader, taxoReader);
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetSumValueSource.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetSumValueSource.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetSumValueSource.cs
new file mode 100644
index 0000000..f90b9f6
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetSumValueSource.cs
@@ -0,0 +1,610 @@
+using System;
+using System.Diagnostics;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Lucene.Net.Randomized.Generators;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * 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 MockAnalyzer = Lucene.Net.Analysis.MockAnalyzer;
+    using Document = Lucene.Net.Documents.Document;
+    using Field = Lucene.Net.Documents.Field;
+    using FloatDocValuesField = Lucene.Net.Documents.FloatDocValuesField;
+    using IntField = Lucene.Net.Documents.IntField;
+    using NumericDocValuesField = Lucene.Net.Documents.NumericDocValuesField;
+    using StringField = Lucene.Net.Documents.StringField;
+    using DirectoryTaxonomyReader = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyReader;
+    using DirectoryTaxonomyWriter = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter;
+    using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext;
+    using DirectoryReader = Lucene.Net.Index.DirectoryReader;
+    using IndexWriter = Lucene.Net.Index.IndexWriter;
+    using IndexWriterConfig = Lucene.Net.Index.IndexWriterConfig;
+    using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter;
+    using Term = Lucene.Net.Index.Term;
+    using FunctionQuery = Lucene.Net.Queries.Function.FunctionQuery;
+    using FunctionValues = Lucene.Net.Queries.Function.FunctionValues;
+    using ValueSource = Lucene.Net.Queries.Function.ValueSource;
+    using DoubleDocValues = Lucene.Net.Queries.Function.DocValues.DoubleDocValues;
+    using FloatFieldSource = Lucene.Net.Queries.Function.ValueSources.FloatFieldSource;
+    using IntFieldSource = Lucene.Net.Queries.Function.ValueSources.IntFieldSource;
+    using LongFieldSource = Lucene.Net.Queries.Function.ValueSources.LongFieldSource;
+    using ConstantScoreQuery = Lucene.Net.Search.ConstantScoreQuery;
+    using IndexSearcher = Lucene.Net.Search.IndexSearcher;
+    using MatchAllDocsQuery = Lucene.Net.Search.MatchAllDocsQuery;
+    using Query = Lucene.Net.Search.Query;
+    using Scorer = Lucene.Net.Search.Scorer;
+    using TermQuery = Lucene.Net.Search.TermQuery;
+    using TopDocs = Lucene.Net.Search.TopDocs;
+    using Directory = Lucene.Net.Store.Directory;
+    using IOUtils = Lucene.Net.Util.IOUtils;
+    using TestUtil = Lucene.Net.Util.TestUtil;
+    using NUnit.Framework;
+    [TestFixture]
+    public class TestTaxonomyFacetSumValueSource : FacetTestCase
+    {
+
+        [Test]
+        public virtual void TestBasic()
+        {
+
+            Store.Directory dir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+
+            // Writes facet ords to a separate directory from the
+            // main index:
+            DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir, IndexWriterConfig.OpenMode_e.CREATE);
+
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir);
+            FacetsConfig config = new FacetsConfig();
+
+            // Reused across documents, to add the necessary facet
+            // fields:
+            Document doc = new Document();
+            doc.Add(new IntField("num", 10, Field.Store.NO));
+            doc.Add(new FacetField("Author", "Bob"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            doc = new Document();
+            doc.Add(new IntField("num", 20, Field.Store.NO));
+            doc.Add(new FacetField("Author", "Lisa"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            doc = new Document();
+            doc.Add(new IntField("num", 30, Field.Store.NO));
+            doc.Add(new FacetField("Author", "Lisa"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            doc = new Document();
+            doc.Add(new IntField("num", 40, Field.Store.NO));
+            doc.Add(new FacetField("Author", "Susan"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            doc = new Document();
+            doc.Add(new IntField("num", 45, Field.Store.NO));
+            doc.Add(new FacetField("Author", "Frank"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            // NRT open
+            IndexSearcher searcher = NewSearcher(writer.Reader);
+            writer.Dispose();
+
+            // NRT open
+            var taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+            taxoWriter.Dispose();
+
+            // Aggregate the facet counts:
+            FacetsCollector c = new FacetsCollector();
+
+            // MatchAllDocsQuery is for "browsing" (counts facets
+            // for all non-deleted docs in the index); normally
+            // you'd use a "normal" query and one of the
+            // Facets.search utility methods:
+            searcher.Search(new MatchAllDocsQuery(), c);
+
+            TaxonomyFacetSumValueSource facets = new TaxonomyFacetSumValueSource(taxoReader, new FacetsConfig(), c, new IntFieldSource("num"));
+
+            // Retrieve & verify results:
+            Assert.AreEqual("dim=Author path=[] value=145.0 childCount=4\n  Lisa (50.0)\n  Frank (45.0)\n  Susan (40.0)\n  Bob (10.0)\n", facets.GetTopChildren(10, "Author").ToString());
+
+            taxoReader.Dispose();
+            searcher.IndexReader.Dispose();
+            dir.Dispose();
+            taxoDir.Dispose();
+        }
+
+        // LUCENE-5333
+        [Test]
+        public virtual void TestSparseFacets()
+        {
+            Store.Directory dir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+
+            // Writes facet ords to a separate directory from the
+            // main index:
+            var taxoWriter = new DirectoryTaxonomyWriter(taxoDir, IndexWriterConfig.OpenMode_e.CREATE);
+
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir);
+            FacetsConfig config = new FacetsConfig();
+
+            Document doc = new Document();
+            doc.Add(new IntField("num", 10, Field.Store.NO));
+            doc.Add(new FacetField("a", "foo1"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            if (Random().NextBoolean())
+            {
+                writer.Commit();
+            }
+
+            doc = new Document();
+            doc.Add(new IntField("num", 20, Field.Store.NO));
+            doc.Add(new FacetField("a", "foo2"));
+            doc.Add(new FacetField("b", "bar1"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            if (Random().NextBoolean())
+            {
+                writer.Commit();
+            }
+
+            doc = new Document();
+            doc.Add(new IntField("num", 30, Field.Store.NO));
+            doc.Add(new FacetField("a", "foo3"));
+            doc.Add(new FacetField("b", "bar2"));
+            doc.Add(new FacetField("c", "baz1"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            // NRT open
+            IndexSearcher searcher = NewSearcher(writer.Reader);
+            writer.Dispose();
+
+            // NRT open
+            var taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+            taxoWriter.Dispose();
+
+            FacetsCollector c = new FacetsCollector();
+            searcher.Search(new MatchAllDocsQuery(), c);
+
+            TaxonomyFacetSumValueSource facets = new TaxonomyFacetSumValueSource(taxoReader, new FacetsConfig(), c, new IntFieldSource("num"));
+
+            // Ask for top 10 labels for any dims that have counts:
+            IList<FacetResult> results = facets.GetAllDims(10);
+
+            Assert.AreEqual(3, results.Count);
+            Assert.AreEqual("dim=a path=[] value=60.0 childCount=3\n  foo3 (30.0)\n  foo2 (20.0)\n  foo1 (10.0)\n", results[0].ToString());
+            Assert.AreEqual("dim=b path=[] value=50.0 childCount=2\n  bar2 (30.0)\n  bar1 (20.0)\n", results[1].ToString());
+            Assert.AreEqual("dim=c path=[] value=30.0 childCount=1\n  baz1 (30.0)\n", results[2].ToString());
+
+            IOUtils.Close(searcher.IndexReader, taxoReader, dir, taxoDir);
+        }
+
+        [Test]
+        public virtual void TestWrongIndexFieldName()
+        {
+
+            Store.Directory dir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+
+            // Writes facet ords to a separate directory from the
+            // main index:
+            var taxoWriter = new DirectoryTaxonomyWriter(taxoDir, IndexWriterConfig.OpenMode_e.CREATE);
+
+            FacetsConfig config = new FacetsConfig();
+            config.SetIndexFieldName("a", "$facets2");
+
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir);
+
+            Document doc = new Document();
+            doc.Add(new IntField("num", 10, Field.Store.NO));
+            doc.Add(new FacetField("a", "foo1"));
+            writer.AddDocument(config.Build(taxoWriter, doc));
+
+            // NRT open
+            IndexSearcher searcher = NewSearcher(writer.Reader);
+            writer.Dispose();
+
+            // NRT open
+            var taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+            taxoWriter.Dispose();
+
+            FacetsCollector c = new FacetsCollector();
+            searcher.Search(new MatchAllDocsQuery(), c);
+
+            TaxonomyFacetSumValueSource facets = new TaxonomyFacetSumValueSource(taxoReader, config, c, new IntFieldSource("num"));
+
+            // Ask for top 10 labels for any dims that have counts:
+            IList<FacetResult> results = facets.GetAllDims(10);
+            Assert.True(results.Count == 0);
+
+            try
+            {
+                facets.GetSpecificValue("a");
+                Fail("should have hit exc");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+
+            try
+            {
+                facets.GetTopChildren(10, "a");
+                Fail("should have hit exc");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+
+            IOUtils.Close(searcher.IndexReader, taxoReader, dir, taxoDir);
+        }
+
+        [Test]
+        public virtual void TestSumScoreAggregator()
+        {
+            Store.Directory indexDir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+
+            DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir);
+            IndexWriter iw = new IndexWriter(indexDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())));
+
+            FacetsConfig config = new FacetsConfig();
+
+            for (int i = AtLeast(30); i > 0; --i)
+            {
+                Document doc = new Document();
+                if (Random().NextBoolean()) // don't match all documents
+                {
+                    doc.Add(new StringField("f", "v", Field.Store.NO));
+                }
+                doc.Add(new FacetField("dim", "a"));
+                iw.AddDocument(config.Build(taxoWriter, doc));
+            }
+
+            DirectoryReader r = DirectoryReader.Open(iw, true);
+            DirectoryTaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+
+            FacetsCollector fc = new FacetsCollector(true);
+            ConstantScoreQuery csq = new ConstantScoreQuery(new MatchAllDocsQuery());
+            csq.Boost = 2.0f;
+
+            TopDocs td = FacetsCollector.Search(NewSearcher(r), csq, 10, fc);
+
+            Facets facets = new TaxonomyFacetSumValueSource(taxoReader, config, fc, new TaxonomyFacetSumValueSource.ScoreValueSource());
+
+            int expected = (int)(td.MaxScore * td.TotalHits);
+            Assert.AreEqual(expected, (int)facets.GetSpecificValue("dim", "a"));
+
+            IOUtils.Close(iw, taxoWriter, taxoReader, taxoDir, r, indexDir);
+        }
+
+        [Test]
+        public virtual void TestNoScore()
+        {
+            Store.Directory indexDir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+
+            DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir);
+            IndexWriter iw = new IndexWriter(indexDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())));
+            FacetsConfig config = new FacetsConfig();
+            for (int i = 0; i < 4; i++)
+            {
+                Document doc = new Document();
+                doc.Add(new NumericDocValuesField("price", (i + 1)));
+                doc.Add(new FacetField("a", Convert.ToString(i % 2)));
+                iw.AddDocument(config.Build(taxoWriter, doc));
+            }
+
+            DirectoryReader r = DirectoryReader.Open(iw, true);
+            DirectoryTaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+
+            FacetsCollector sfc = new FacetsCollector();
+            NewSearcher(r).Search(new MatchAllDocsQuery(), sfc);
+            Facets facets = new TaxonomyFacetSumValueSource(taxoReader, config, sfc, new LongFieldSource("price"));
+            Assert.AreEqual("dim=a path=[] value=10.0 childCount=2\n  1 (6.0)\n  0 (4.0)\n", facets.GetTopChildren(10, "a").ToString());
+
+            IOUtils.Close(taxoWriter, iw, taxoReader, taxoDir, r, indexDir);
+        }
+
+        [Test]
+        public virtual void TestWithScore()
+        {
+            Store.Directory indexDir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+
+            DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir);
+            IndexWriter iw = new IndexWriter(indexDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())));
+
+            FacetsConfig config = new FacetsConfig();
+            for (int i = 0; i < 4; i++)
+            {
+                Document doc = new Document();
+                doc.Add(new NumericDocValuesField("price", (i + 1)));
+                doc.Add(new FacetField("a", Convert.ToString(i % 2)));
+                iw.AddDocument(config.Build(taxoWriter, doc));
+            }
+
+            DirectoryReader r = DirectoryReader.Open(iw, true);
+            DirectoryTaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+
+            ValueSource valueSource = new ValueSourceAnonymousInnerClassHelper(this);
+
+            FacetsCollector fc = new FacetsCollector(true);
+            // score documents by their 'price' field - makes asserting the correct counts for the categories easier
+            Query q = new FunctionQuery(new LongFieldSource("price"));
+            FacetsCollector.Search(NewSearcher(r), q, 10, fc);
+            Facets facets = new TaxonomyFacetSumValueSource(taxoReader, config, fc, valueSource);
+
+            Assert.AreEqual("dim=a path=[] value=10.0 childCount=2\n  1 (6.0)\n  0 (4.0)\n", facets.GetTopChildren(10, "a").ToString());
+
+            IOUtils.Close(taxoWriter, iw, taxoReader, taxoDir, r, indexDir);
+        }
+
+        private class ValueSourceAnonymousInnerClassHelper : ValueSource
+        {
+            private readonly TestTaxonomyFacetSumValueSource outerInstance;
+
+            public ValueSourceAnonymousInnerClassHelper(TestTaxonomyFacetSumValueSource outerInstance)
+            {
+                this.outerInstance = outerInstance;
+            }
+
+            public override FunctionValues GetValues(IDictionary context, AtomicReaderContext readerContext)
+            {
+                Scorer scorer = (Scorer)context["scorer"];
+                Debug.Assert(scorer != null);
+                return new DoubleDocValuesAnonymousInnerClassHelper(this, scorer);
+            }
+
+            private class DoubleDocValuesAnonymousInnerClassHelper : DoubleDocValues
+            {
+                private readonly ValueSourceAnonymousInnerClassHelper outerInstance;
+
+                private Scorer scorer;
+
+                public DoubleDocValuesAnonymousInnerClassHelper(ValueSourceAnonymousInnerClassHelper outerInstance, Scorer scorer)
+                    : base(null) //todo: value source
+                {
+                    this.outerInstance = outerInstance;
+                    this.scorer = scorer;
+                }
+
+
+
+                public override double DoubleVal(int document)
+                {
+                    try
+                    {
+                        return scorer.Score();
+                    }
+                    catch (IOException exception)
+                    {
+                        throw new Exception(exception.Message, exception);
+                    }
+                }
+            }
+
+            public override bool Equals(object o)
+            {
+                return o == this;
+            }
+            public override int GetHashCode()
+            {
+                throw new NotImplementedException();
+            }
+
+            public override string Description
+            {
+                get
+                {
+                    return "score()";
+                }
+
+            }
+        }
+
+        [Test]
+        public virtual void TestRollupValues()
+        {
+            Store.Directory indexDir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+
+            var taxoWriter = new DirectoryTaxonomyWriter(taxoDir);
+            IndexWriter iw = new IndexWriter(indexDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())));
+            FacetsConfig config = new FacetsConfig();
+            config.SetHierarchical("a", true);
+            //config.setRequireDimCount("a", true);
+
+            for (int i = 0; i < 4; i++)
+            {
+                Document doc = new Document();
+                doc.Add(new NumericDocValuesField("price", (i + 1)));
+                doc.Add(new FacetField("a", Convert.ToString(i % 2), "1"));
+                iw.AddDocument(config.Build(taxoWriter, doc));
+            }
+
+            DirectoryReader r = DirectoryReader.Open(iw, true);
+            DirectoryTaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+
+            ValueSource valueSource = new LongFieldSource("price");
+            FacetsCollector sfc = new FacetsCollector();
+            NewSearcher(r).Search(new MatchAllDocsQuery(), sfc);
+            Facets facets = new TaxonomyFacetSumValueSource(taxoReader, config, sfc, valueSource);
+
+            Assert.AreEqual("dim=a path=[] value=10.0 childCount=2\n  1 (6.0)\n  0 (4.0)\n", facets.GetTopChildren(10, "a").ToString());
+
+            IOUtils.Close(taxoWriter, iw, taxoReader, taxoDir, r, indexDir);
+        }
+
+        [Test]
+        public virtual void TestCountAndSumScore()
+        {
+            Store.Directory indexDir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+
+            var taxoWriter = new DirectoryTaxonomyWriter(taxoDir);
+            IndexWriter iw = new IndexWriter(indexDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())));
+            FacetsConfig config = new FacetsConfig();
+            config.SetIndexFieldName("b", "$b");
+
+            for (int i = AtLeast(30); i > 0; --i)
+            {
+                Document doc = new Document();
+                doc.Add(new StringField("f", "v", Field.Store.NO));
+                doc.Add(new FacetField("a", "1"));
+                doc.Add(new FacetField("b", "1"));
+                iw.AddDocument(config.Build(taxoWriter, doc));
+            }
+
+            DirectoryReader r = DirectoryReader.Open(iw, true);
+            var taxoReader = new DirectoryTaxonomyReader(taxoWriter);
+
+            FacetsCollector fc = new FacetsCollector(true);
+            FacetsCollector.Search(NewSearcher(r), new MatchAllDocsQuery(), 10, fc);
+
+            Facets facets1 = GetTaxonomyFacetCounts(taxoReader, config, fc);
+            Facets facets2 = new TaxonomyFacetSumValueSource(new DocValuesOrdinalsReader("$b"), taxoReader, config, fc, new TaxonomyFacetSumValueSource.ScoreValueSource());
+
+            Assert.AreEqual(r.MaxDoc, (int)facets1.GetTopChildren(10, "a").value);
+            Assert.AreEqual(r.MaxDoc, (double)facets2.GetTopChildren(10, "b").value, 1E-10);
+            IOUtils.Close(taxoWriter, iw, taxoReader, taxoDir, r, indexDir);
+        }
+
+        [Test]
+        public virtual void TestRandom()
+        {
+            string[] tokens = GetRandomTokens(10);
+            Store.Directory indexDir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+
+            RandomIndexWriter w = new RandomIndexWriter(Random(), indexDir);
+            var tw = new DirectoryTaxonomyWriter(taxoDir);
+            FacetsConfig config = new FacetsConfig();
+            int numDocs = AtLeast(1000);
+            int numDims = TestUtil.NextInt(Random(), 1, 7);
+            IList<TestDoc> testDocs = GetRandomDocs(tokens, numDocs, numDims);
+            foreach (TestDoc testDoc in testDocs)
+            {
+                Document doc = new Document();
+                doc.Add(NewStringField("content", testDoc.content, Field.Store.NO));
+                testDoc.value = Random().NextFloat();
+                doc.Add(new FloatDocValuesField("value", testDoc.value));
+                for (int j = 0; j < numDims; j++)
+                {
+                    if (testDoc.dims[j] != null)
+                    {
+                        doc.Add(new FacetField("dim" + j, testDoc.dims[j]));
+                    }
+                }
+                w.AddDocument(config.Build(tw, doc));
+            }
+
+            // NRT open
+            IndexSearcher searcher = NewSearcher(w.Reader);
+
+            // NRT open
+            var tr = new DirectoryTaxonomyReader(tw);
+
+            ValueSource values = new FloatFieldSource("value");
+
+            int iters = AtLeast(100);
+            for (int iter = 0; iter < iters; iter++)
+            {
+                string searchToken = tokens[Random().Next(tokens.Length)];
+                if (VERBOSE)
+                {
+                    Console.WriteLine("\nTEST: iter content=" + searchToken);
+                }
+                FacetsCollector fc = new FacetsCollector();
+                FacetsCollector.Search(searcher, new TermQuery(new Term("content", searchToken)), 10, fc);
+                Facets facets = new TaxonomyFacetSumValueSource(tr, config, fc, values);
+
+                // Slow, yet hopefully bug-free, faceting:
+                var expectedValues = new List<Dictionary<string, float?>>();
+                for (int i = 0; i < numDims; i++)
+                {
+                    expectedValues[i] = new Dictionary<string, float?>();
+                }
+
+                foreach (TestDoc doc in testDocs)
+                {
+                    if (doc.content.Equals(searchToken))
+                    {
+                        for (int j = 0; j < numDims; j++)
+                        {
+                            if (doc.dims[j] != null)
+                            {
+                                float? v = expectedValues[j][doc.dims[j]];
+                                if (v == null)
+                                {
+                                    expectedValues[j][doc.dims[j]] = doc.value;
+                                }
+                                else
+                                {
+                                    expectedValues[j][doc.dims[j]] = (float)v + doc.value;
+                                }
+                            }
+                        }
+                    }
+                }
+
+                IList<FacetResult> expected = new List<FacetResult>();
+                for (int i = 0; i < numDims; i++)
+                {
+                    IList<LabelAndValue> labelValues = new List<LabelAndValue>();
+                    float totValue = 0;
+                    foreach (KeyValuePair<string, float?> ent in expectedValues[i])
+                    {
+                        labelValues.Add(new LabelAndValue(ent.Key, ent.Value.Value));
+                        totValue += ent.Value.Value;
+                    }
+                    SortLabelValues(labelValues);
+                    if (totValue > 0)
+                    {
+                        expected.Add(new FacetResult("dim" + i, new string[0], totValue, labelValues.ToArray(), labelValues.Count));
+                    }
+                }
+
+                // Sort by highest value, tie break by value:
+                SortFacetResults(expected);
+
+                IList<FacetResult> actual = facets.GetAllDims(10);
+
+                // Messy: fixup ties
+                SortTies(actual);
+
+                if (VERBOSE)
+                {
+                    Console.WriteLine("expected=\n" + expected.ToString());
+                    Console.WriteLine("actual=\n" + actual.ToString());
+                }
+
+                AssertFloatValuesEquals(expected, actual);
+            }
+
+            IOUtils.Close(w, tw, searcher.IndexReader, tr, indexDir, taxoDir);
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/Taxonomy/WriterCache/TestCharBlockArray.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/WriterCache/TestCharBlockArray.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/WriterCache/TestCharBlockArray.cs
new file mode 100644
index 0000000..f4be988
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/WriterCache/TestCharBlockArray.cs
@@ -0,0 +1,110 @@
+using System.Text;
+using Lucene.Net.Support;
+using NUnit.Framework;
+
+namespace Lucene.Net.Facet.Taxonomy.WriterCache
+{
+
+
+    using TestUtil = Lucene.Net.Util.TestUtil;
+
+    /*
+     * 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.
+     */
+    [TestFixture]
+    public class TestCharBlockArray : FacetTestCase
+    {
+
+        /* not finished yet because of missing charset decoder */
+
+        /*
+        public virtual void testArray()
+        {
+            CharBlockArray array = new CharBlockArray();
+            StringBuilder builder = new StringBuilder();
+
+            const int n = 100 * 1000;
+
+            byte[] buffer = new byte[50];
+
+            for (int i = 0; i < n; i++)
+            {
+                Random().NextBytes(buffer);
+                int size = 1 + Random().Next(50);
+                // This test is turning random bytes into a string,
+                // this is asking for trouble.
+
+                CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder().onUnmappableCharacter(CodingErrorAction.REPLACE).onMalformedInput(CodingErrorAction.REPLACE);
+                string s = decoder.Decode(ByteBuffer.Wrap(buffer, 0, size)).ToString();
+                array.append(s);
+                builder.Append(s);
+            }
+
+            for (int i = 0; i < n; i++)
+            {
+                Random().NextBytes(buffer);
+                int size = 1 + Random().Next(50);
+                // This test is turning random bytes into a string,
+                // this is asking for trouble.
+                CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder().onUnmappableCharacter(CodingErrorAction.REPLACE).onMalformedInput(CodingErrorAction.REPLACE);
+                string s = decoder.decode(ByteBuffer.Wrap(buffer, 0, size)).ToString();
+                array.append((CharSequence)s);
+                builder.Append(s);
+            }
+
+            for (int i = 0; i < n; i++)
+            {
+                Random().NextBytes(buffer);
+                int size = 1 + Random().Next(50);
+                // This test is turning random bytes into a string,
+                // this is asking for trouble.
+                CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder().onUnmappableCharacter(CodingErrorAction.REPLACE).onMalformedInput(CodingErrorAction.REPLACE);
+                string s = decoder.decode(ByteBuffer.Wrap(buffer, 0, size)).ToString();
+                for (int j = 0; j < s.Length; j++)
+                {
+                    array.append(s[j]);
+                }
+                builder.Append(s);
+            }
+
+            AssertEqualsInternal("GrowingCharArray<->StringBuilder mismatch.", builder, array);
+
+            File tempDir = CreateTempDir("growingchararray");
+            File f = new File(tempDir, "GrowingCharArrayTest.tmp");
+            BufferedOutputStream @out = new BufferedOutputStream(new FileOutputStream(f));
+            array.flush(@out);
+            @out.flush();
+            @out.Close();
+
+            BufferedInputStream @in = new BufferedInputStream(new FileInputStream(f));
+            array = CharBlockArray.open(@in);
+            AssertEqualsInternal("GrowingCharArray<->StringBuilder mismatch after flush/load.", builder, array);
+            @in.Close();
+            f.delete();
+        }
+
+        private static void AssertEqualsInternal(string msg, StringBuilder expected, CharBlockArray actual)
+        {
+            Assert.AreEqual(expected.Length, actual.Length, msg);
+            for (int i = 0; i < expected.Length; i++)
+            {
+                Assert.AreEqual(expected[i], actual.CharAt(i), msg);
+            }
+        }
+        */
+    }
+
+}
\ No newline at end of file


[04/26] lucenenet git commit: Merge branch 'master' of https://github.com/eladmarg/lucene.net

Posted by sy...@apache.org.
Merge branch 'master' of https://github.com/eladmarg/lucene.net


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

Branch: refs/heads/master
Commit: 931d42efd93834867add115b7229bdc5ed2901fe
Parents: ca463d3 6d9a6ff
Author: eladmarg <el...@gmail.com>
Authored: Sat Nov 8 17:46:39 2014 -0800
Committer: eladmarg <el...@gmail.com>
Committed: Sat Nov 8 17:46:39 2014 -0800

----------------------------------------------------------------------

----------------------------------------------------------------------



[24/26] lucenenet git commit: fix test TestMergeStability

Posted by sy...@apache.org.
fix test TestMergeStability


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

Branch: refs/heads/master
Commit: eabf7f287b9e71233ceb72f53f88126946628f24
Parents: f1262cd
Author: eladmarg <el...@gmail.com>
Authored: Thu Nov 13 23:36:54 2014 -0800
Committer: eladmarg <el...@gmail.com>
Committed: Thu Nov 13 23:36:54 2014 -0800

----------------------------------------------------------------------
 .../Index/BaseIndexFileFormatTestCase.cs            | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucenenet/blob/eabf7f28/src/Lucene.Net.TestFramework/Index/BaseIndexFileFormatTestCase.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.TestFramework/Index/BaseIndexFileFormatTestCase.cs b/src/Lucene.Net.TestFramework/Index/BaseIndexFileFormatTestCase.cs
index 693540e..264d1ef 100644
--- a/src/Lucene.Net.TestFramework/Index/BaseIndexFileFormatTestCase.cs
+++ b/src/Lucene.Net.TestFramework/Index/BaseIndexFileFormatTestCase.cs
@@ -65,10 +65,20 @@ namespace Lucene.Net.Index
             foreach (string file in d.ListAll())
             {
                 string ext = IndexFileNames.GetExtension(file);
-                long previousLength = bytesUsedByExtension.ContainsKey(ext) ? bytesUsedByExtension[ext] : 0;
-                bytesUsedByExtension[ext] = previousLength + d.FileLength(file);
+                if (!string.IsNullOrWhiteSpace(ext))
+                {
+                    long previousLength = bytesUsedByExtension.ContainsKey(ext) ? bytesUsedByExtension[ext] : 0;
+                    bytesUsedByExtension[ext] = previousLength + d.FileLength(file);
+                }
+            }
+
+            foreach (var elm in ExcludedExtensionsFromByteCounts())
+            {
+                if (bytesUsedByExtension.Keys.Contains(elm))
+                {
+                    bytesUsedByExtension.Remove(elm);
+                }
             }
-            bytesUsedByExtension.Keys.RemoveAll(ExcludedExtensionsFromByteCounts());
             return bytesUsedByExtension;
         }
 


[13/26] lucenenet git commit: first commit of facet porting, failing tests will be fixed in next commits.

Posted by sy...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/TaxonomyReader.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/TaxonomyReader.cs b/Lucene.Net.Facet/Taxonomy/TaxonomyReader.cs
new file mode 100644
index 0000000..aaee8fe
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/TaxonomyReader.cs
@@ -0,0 +1,316 @@
+using System;
+using System.Diagnostics;
+using System.Collections.Generic;
+using System.Threading;
+using Lucene.Net.Support;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+
+    using AlreadyClosedException = Lucene.Net.Store.AlreadyClosedException;
+
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// TaxonomyReader is the read-only interface with which the faceted-search
+    /// library uses the taxonomy during search time.
+    /// <P>
+    /// A TaxonomyReader holds a list of categories. Each category has a serial
+    /// number which we call an "ordinal", and a hierarchical "path" name:
+    /// <UL>
+    /// <LI>
+    /// The ordinal is an integer that starts at 0 for the first category (which is
+    /// always the root category), and grows contiguously as more categories are
+    /// added; Note that once a category is added, it can never be deleted.
+    /// <LI>
+    /// The path is a CategoryPath object specifying the category's position in the
+    /// hierarchy.
+    /// </UL>
+    /// <B>Notes about concurrent access to the taxonomy:</B>
+    /// <P>
+    /// An implementation must allow multiple readers to be active concurrently
+    /// with a single writer. Readers follow so-called "point in time" semantics,
+    /// i.e., a TaxonomyReader object will only see taxonomy entries which were
+    /// available at the time it was created. What the writer writes is only
+    /// available to (new) readers after the writer's commit() is called.
+    /// <P>
+    /// In faceted search, two separate indices are used: the main Lucene index,
+    /// and the taxonomy. Because the main index refers to the categories listed
+    /// in the taxonomy, it is important to open the taxonomy *after* opening the
+    /// main index, and it is also necessary to reopen() the taxonomy after
+    /// reopen()ing the main index.
+    /// <P>
+    /// This order is important, otherwise it would be possible for the main index
+    /// to refer to a category which is not yet visible in the old snapshot of
+    /// the taxonomy. Note that it is indeed fine for the the taxonomy to be opened
+    /// after the main index - even a long time after. The reason is that once
+    /// a category is added to the taxonomy, it can never be changed or deleted,
+    /// so there is no danger that a "too new" taxonomy not being consistent with
+    /// an older index.
+    /// 
+    /// @lucene.experimental
+    /// </summary>
+    public abstract class TaxonomyReader
+    {
+
+        /// <summary>
+        /// An iterator over a category's children. </summary>
+        public class ChildrenIterator
+        {
+
+            internal readonly int[] siblings;
+            internal int child;
+
+            internal ChildrenIterator(int child, int[] siblings)
+            {
+                this.siblings = siblings;
+                this.child = child;
+            }
+
+            /// <summary>
+            /// Return the next child ordinal, or <seealso cref="TaxonomyReader#INVALID_ORDINAL"/>
+            /// if no more children.
+            /// </summary>
+            public virtual int Next()
+            {
+                int res = child;
+                if (child != TaxonomyReader.INVALID_ORDINAL)
+                {
+                    child = siblings[child];
+                }
+                return res;
+            }
+
+        }
+
+        /// <summary>
+        /// Sole constructor. </summary>
+        public TaxonomyReader()
+        {
+        }
+
+        /// <summary>
+        /// The root category (the category with the empty path) always has the ordinal
+        /// 0, to which we give a name ROOT_ORDINAL. <seealso cref="#getOrdinal(FacetLabel)"/>
+        /// of an empty path will always return {@code ROOT_ORDINAL}, and
+        /// <seealso cref="#getPath(int)"/> with {@code ROOT_ORDINAL} will return the empty path.
+        /// </summary>
+        public const int ROOT_ORDINAL = 0;
+
+        /// <summary>
+        /// Ordinals are always non-negative, so a negative ordinal can be used to
+        /// signify an error. Methods here return INVALID_ORDINAL (-1) in this case.
+        /// </summary>
+        public const int INVALID_ORDINAL = -1;
+
+        /// <summary>
+        /// If the taxonomy has changed since the provided reader was opened, open and
+        /// return a new <seealso cref="TaxonomyReader"/>; else, return {@code null}. The new
+        /// reader, if not {@code null}, will be the same type of reader as the one
+        /// given to this method.
+        /// 
+        /// <para>
+        /// This method is typically far less costly than opening a fully new
+        /// <seealso cref="TaxonomyReader"/> as it shares resources with the provided
+        /// <seealso cref="TaxonomyReader"/>, when possible.
+        /// </para>
+        /// </summary>
+        public static T OpenIfChanged<T>(T oldTaxoReader) where T : TaxonomyReader
+        {
+            T newTaxoReader = (T)oldTaxoReader.DoOpenIfChanged();
+            Debug.Assert(newTaxoReader != oldTaxoReader);
+            return newTaxoReader;
+        }
+
+        private volatile bool Closed = false;
+
+        // set refCount to 1 at start
+        private readonly AtomicInteger refCount = new AtomicInteger(1);
+
+        /// <summary>
+        /// performs the actual task of closing the resources that are used by the
+        /// taxonomy reader.
+        /// </summary>
+        protected internal abstract void DoClose();
+
+        /// <summary>
+        /// Implements the actual opening of a new <seealso cref="TaxonomyReader"/> instance if
+        /// the taxonomy has changed.
+        /// </summary>
+        /// <seealso cref= #openIfChanged(TaxonomyReader) </seealso>
+        protected internal abstract TaxonomyReader DoOpenIfChanged();
+
+        /// <summary>
+        /// Throws <seealso cref="AlreadyClosedException"/> if this IndexReader is closed
+        /// </summary>
+        protected internal void EnsureOpen()
+        {
+            if (RefCount <= 0)
+            {
+                throw new AlreadyClosedException("this TaxonomyReader is closed");
+            }
+        }
+
+        public virtual void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                lock (this)
+                {
+                    if (!Closed)
+                    {
+                        DecRef();
+                        Closed = true;
+                    }
+                }
+            }
+        }
+
+
+        /// <summary>
+        /// Expert: decreases the refCount of this TaxonomyReader instance. If the
+        /// refCount drops to 0 this taxonomy reader is closed.
+        /// </summary>
+        public void DecRef()
+        {
+            EnsureOpen();
+            int rc = refCount.DecrementAndGet();
+            if (rc == 0)
+            {
+                bool success = false;
+                try
+                {
+                    DoClose();
+                    Closed = true;
+                    success = true;
+                }
+                finally
+                {
+                    if (!success)
+                    {
+                        // Put reference back on failure
+                        refCount.IncrementAndGet();
+                    }
+                }
+            }
+            else if (rc < 0)
+            {
+                throw new ThreadStateException("too many decRef calls: refCount is " + rc + " after decrement");
+            }
+        }
+
+        /// <summary>
+        /// Returns a <seealso cref="ParallelTaxonomyArrays"/> object which can be used to
+        /// efficiently traverse the taxonomy tree.
+        /// </summary>
+        public abstract ParallelTaxonomyArrays ParallelTaxonomyArrays { get; }
+
+        /// <summary>
+        /// Returns an iterator over the children of the given ordinal. </summary>
+        public virtual ChildrenIterator GetChildren(int ordinal)
+        {
+            ParallelTaxonomyArrays arrays = ParallelTaxonomyArrays;
+            int child = ordinal >= 0 ? arrays.Children()[ordinal] : INVALID_ORDINAL;
+            return new ChildrenIterator(child, arrays.Siblings());
+        }
+
+        /// <summary>
+        /// Retrieve user committed data.
+        /// </summary>
+        /// <seealso cref= TaxonomyWriter#setCommitData(Map) </seealso>
+        public abstract IDictionary<string, string> CommitUserData { get; }
+
+        /// <summary>
+        /// Returns the ordinal of the category given as a path. The ordinal is the
+        /// category's serial number, an integer which starts with 0 and grows as more
+        /// categories are added (note that once a category is added, it can never be
+        /// deleted).
+        /// </summary>
+        /// <returns> the category's ordinal or <seealso cref="#INVALID_ORDINAL"/> if the category
+        ///         wasn't foun. </returns>
+        public abstract int GetOrdinal(FacetLabel categoryPath);
+
+        /// <summary>
+        /// Returns ordinal for the dim + path. </summary>
+        public virtual int GetOrdinal(string dim, string[] path)
+        {
+            string[] fullPath = new string[path.Length + 1];
+            fullPath[0] = dim;
+            Array.Copy(path, 0, fullPath, 1, path.Length);
+            return GetOrdinal(new FacetLabel(fullPath));
+        }
+
+        /// <summary>
+        /// Returns the path name of the category with the given ordinal. </summary>
+        public abstract FacetLabel GetPath(int ordinal);
+
+        /// <summary>
+        /// Returns the current refCount for this taxonomy reader. </summary>
+        public int RefCount
+        {
+            get
+            {
+                return refCount.Get();
+            }
+        }
+
+        /// <summary>
+        /// Returns the number of categories in the taxonomy. Note that the number of
+        /// categories returned is often slightly higher than the number of categories
+        /// inserted into the taxonomy; This is because when a category is added to the
+        /// taxonomy, its ancestors are also added automatically (including the root,
+        /// which always get ordinal 0).
+        /// </summary>
+        public abstract int Size { get; }
+
+        /// <summary>
+        /// Expert: increments the refCount of this TaxonomyReader instance. RefCounts
+        /// can be used to determine when a taxonomy reader can be closed safely, i.e.
+        /// as soon as there are no more references. Be sure to always call a
+        /// corresponding decRef(), in a finally clause; otherwise the reader may never
+        /// be closed.
+        /// </summary>
+        public void IncRef()
+        {
+            EnsureOpen();
+            refCount.IncrementAndGet();
+        }
+
+        /// <summary>
+        /// Expert: increments the refCount of this TaxonomyReader
+        ///  instance only if it has not been closed yet.  Returns
+        ///  true on success. 
+        /// </summary>
+        public bool TryIncRef()
+        {
+            int count;
+            while ((count = refCount.Get()) > 0)
+            {
+                if (refCount.CompareAndSet(count, count + 1))
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/TaxonomyWriter.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/TaxonomyWriter.cs b/Lucene.Net.Facet/Taxonomy/TaxonomyWriter.cs
new file mode 100644
index 0000000..26a3e10
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/TaxonomyWriter.cs
@@ -0,0 +1,125 @@
+using System;
+using System.Collections.Generic;
+using Lucene.Net.Index;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+
+    using TwoPhaseCommit = Lucene.Net.Index.TwoPhaseCommit;
+
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// TaxonomyWriter is the interface which the faceted-search library uses
+    /// to dynamically build the taxonomy at indexing time.
+    /// <P>
+    /// Notes about concurrent access to the taxonomy:
+    /// <P>
+    /// An implementation must allow multiple readers and a single writer to be
+    /// active concurrently. Readers follow so-called "point in time" semantics,
+    /// i.e., a reader object will only see taxonomy entries which were available
+    /// at the time it was created. What the writer writes is only available to
+    /// (new) readers after the writer's commit() is called.
+    /// <P>
+    /// Faceted search keeps two indices - namely Lucene's main index, and this
+    /// taxonomy index. When one or more readers are active concurrently with the
+    /// writer, care must be taken to avoid an inconsistency between the state of
+    /// these two indices: When writing to the indices, the taxonomy must always
+    /// be committed to disk *before* the main index, because the main index
+    /// refers to categories listed in the taxonomy.
+    /// Such control can best be achieved by turning off the main index's
+    /// "autocommit" feature, and explicitly calling commit() for both indices
+    /// (first for the taxonomy, then for the main index).
+    /// In old versions of Lucene (2.2 or earlier), when autocommit could not be
+    /// turned off, a more complicated solution needs to be used. E.g., use
+    /// some sort of (possibly inter-process) locking to ensure that a reader
+    /// is being opened only right after both indices have been flushed (and
+    /// before anything else is written to them).
+    /// 
+    /// @lucene.experimental
+    /// </summary>
+    public interface TaxonomyWriter : IDisposable, TwoPhaseCommit
+    {
+
+        /// <summary>
+        /// addCategory() adds a category with a given path name to the taxonomy,
+        /// and returns its ordinal. If the category was already present in
+        /// the taxonomy, its existing ordinal is returned.
+        /// <P>
+        /// Before adding a category, addCategory() makes sure that all its
+        /// ancestor categories exist in the taxonomy as well. As result, the
+        /// ordinal of a category is guaranteed to be smaller then the ordinal of
+        /// any of its descendants. 
+        /// </summary>
+        int AddCategory(FacetLabel categoryPath);
+
+        /// <summary>
+        /// getParent() returns the ordinal of the parent category of the category
+        /// with the given ordinal.
+        /// <P>
+        /// When a category is specified as a path name, finding the path of its
+        /// parent is as trivial as dropping the last component of the path.
+        /// getParent() is functionally equivalent to calling getPath() on the
+        /// given ordinal, dropping the last component of the path, and then calling
+        /// getOrdinal() to get an ordinal back. 
+        /// <P>
+        /// If the given ordinal is the ROOT_ORDINAL, an INVALID_ORDINAL is returned.
+        /// If the given ordinal is a top-level category, the ROOT_ORDINAL is returned.
+        /// If an invalid ordinal is given (negative or beyond the last available
+        /// ordinal), an ArrayIndexOutOfBoundsException is thrown. However, it is
+        /// expected that getParent will only be called for ordinals which are
+        /// already known to be in the taxonomy.
+        /// TODO (Facet): instead of a getParent(ordinal) method, consider having a
+        /// <P>
+        /// getCategory(categorypath, prefixlen) which is similar to addCategory
+        /// except it doesn't add new categories; This method can be used to get
+        /// the ordinals of all prefixes of the given category, and it can use
+        /// exactly the same code and cache used by addCategory() so it means less code.
+        /// </summary>
+        int GetParent(int ordinal);
+
+        /// <summary>
+        /// getSize() returns the number of categories in the taxonomy.
+        /// <P>
+        /// Because categories are numbered consecutively starting with 0, it
+        /// means the taxonomy contains ordinals 0 through getSize()-1.
+        /// <P>
+        /// Note that the number returned by getSize() is often slightly higher
+        /// than the number of categories inserted into the taxonomy; This is
+        /// because when a category is added to the taxonomy, its ancestors
+        /// are also added automatically (including the root, which always get
+        /// ordinal 0).
+        /// </summary>
+        int Size { get; }
+
+        /// <summary>
+        /// Sets the commit user data map. That method is considered a transaction and
+        /// will be <seealso cref="#commit() committed"/> even if no other changes were made to
+        /// the writer instance.
+        /// <para>
+        /// <b>NOTE:</b> the map is cloned internally, therefore altering the map's
+        /// contents after calling this method has no effect.
+        /// </para>
+        /// </summary>
+        IDictionary<string, string> CommitData { set; get; }
+
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/WriterCache/CategoryPathUtils.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/WriterCache/CategoryPathUtils.cs b/Lucene.Net.Facet/Taxonomy/WriterCache/CategoryPathUtils.cs
new file mode 100644
index 0000000..36e5db1
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/WriterCache/CategoryPathUtils.cs
@@ -0,0 +1,99 @@
+namespace Lucene.Net.Facet.Taxonomy.WriterCache
+{
+
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// Utilities for use of <seealso cref="FacetLabel"/> by <seealso cref="CompactLabelToOrdinal"/>. </summary>
+    internal class CategoryPathUtils
+    {
+
+        /// <summary>
+        /// Serializes the given <seealso cref="FacetLabel"/> to the <seealso cref="CharBlockArray"/>. </summary>
+        public static void Serialize(FacetLabel cp, CharBlockArray charBlockArray)
+        {
+            charBlockArray.Append((char)cp.length);
+            if (cp.length == 0)
+            {
+                return;
+            }
+            for (int i = 0; i < cp.length; i++)
+            {
+                charBlockArray.Append((char)cp.components[i].Length);
+                charBlockArray.Append(cp.components[i]);
+            }
+        }
+
+        /// <summary>
+        /// Calculates a hash function of a path that was serialized with
+        /// <seealso cref="#serialize(FacetLabel, CharBlockArray)"/>.
+        /// </summary>
+        public static int HashCodeOfSerialized(CharBlockArray charBlockArray, int offset)
+        {
+            int length = charBlockArray.CharAt(offset++);
+            if (length == 0)
+            {
+                return 0;
+            }
+
+            int hash = length;
+            for (int i = 0; i < length; i++)
+            {
+                int len = charBlockArray.CharAt(offset++);
+                hash = hash * 31 + charBlockArray.SubSequence(offset, offset + len).GetHashCode();
+                offset += len;
+            }
+            return hash;
+        }
+
+        /// <summary>
+        /// Check whether the <seealso cref="FacetLabel"/> is equal to the one serialized in
+        /// <seealso cref="CharBlockArray"/>.
+        /// </summary>
+        public static bool EqualsToSerialized(FacetLabel cp, CharBlockArray charBlockArray, int offset)
+        {
+            int n = charBlockArray.CharAt(offset++);
+            if (cp.length != n)
+            {
+                return false;
+            }
+            if (cp.length == 0)
+            {
+                return true;
+            }
+
+            for (int i = 0; i < cp.length; i++)
+            {
+                int len = charBlockArray.CharAt(offset++);
+                if (len != cp.components[i].Length)
+                {
+                    return false;
+                }
+
+                if (!cp.components[i].Equals(charBlockArray.SubSequence(offset, offset + len)))
+                {
+                    return false;
+                }
+                offset += len;
+            }
+            return true;
+        }
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/WriterCache/CharBlockArray.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/WriterCache/CharBlockArray.cs b/Lucene.Net.Facet/Taxonomy/WriterCache/CharBlockArray.cs
new file mode 100644
index 0000000..4695eb0
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/WriterCache/CharBlockArray.cs
@@ -0,0 +1,234 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using Lucene.Net.Store;
+using Lucene.Net.Support;
+
+namespace Lucene.Net.Facet.Taxonomy.WriterCache
+{
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// Similar to <seealso cref="StringBuilder"/>, but with a more efficient growing strategy.
+    /// This class uses char array blocks to grow.
+    /// 
+    /// @lucene.experimental
+    /// </summary>
+    [Serializable]
+    public class CharBlockArray : ICharSequence
+    {
+
+        private const long serialVersionUID = 1L;
+
+        private const int DefaultBlockSize = 32 * 1024; // 32 KB default size
+
+        [Serializable]
+        internal sealed class Block : ICloneable
+        {
+            internal const long serialVersionUID = 1L;
+
+            internal readonly char[] chars;
+            internal int length;
+
+            internal Block(int size)
+            {
+                this.chars = new char[size];
+                this.length = 0;
+            }
+
+            public object Clone()
+            {
+                throw new NotImplementedException();
+            }
+        }
+
+        internal IList<Block> blocks;
+        internal Block current;
+        internal int blockSize;
+        internal int length_Renamed;
+
+        public CharBlockArray()
+            : this(DefaultBlockSize)
+        {
+        }
+
+        internal CharBlockArray(int blockSize)
+        {
+            this.blocks = new List<Block>();
+            this.blockSize = blockSize;
+            AddBlock();
+        }
+
+        private void AddBlock()
+        {
+            this.current = new Block(this.blockSize);
+            this.blocks.Add(this.current);
+        }
+
+        internal virtual int blockIndex(int index)
+        {
+            return index / blockSize;
+        }
+
+        internal virtual int IndexInBlock(int index)
+        {
+            return index % blockSize;
+        }
+
+        public CharBlockArray Append(ICharSequence chars)
+        {
+            return Append(chars, 0, chars.Length);
+        }
+
+        public CharBlockArray Append(char c)
+        {
+            if (this.current.length == this.blockSize)
+            {
+                AddBlock();
+            }
+            this.current.chars[this.current.length++] = c;
+            this.length_Renamed++;
+
+            return this;
+        }
+
+        public CharBlockArray Append(ICharSequence chars, int start, int length)
+        {
+            int end = start + length;
+            for (int i = start; i < end; i++)
+            {
+                Append(chars.CharAt(i));
+            }
+            return this;
+        }
+
+        public virtual CharBlockArray Append(char[] chars, int start, int length)
+        {
+            int offset = start;
+            int remain = length;
+            while (remain > 0)
+            {
+                if (this.current.length == this.blockSize)
+                {
+                    AddBlock();
+                }
+                int toCopy = remain;
+                int remainingInBlock = this.blockSize - this.current.length;
+                if (remainingInBlock < toCopy)
+                {
+                    toCopy = remainingInBlock;
+                }
+                Array.Copy(chars, offset, this.current.chars, this.current.length, toCopy);
+                offset += toCopy;
+                remain -= toCopy;
+                this.current.length += toCopy;
+            }
+
+            this.length_Renamed += length;
+            return this;
+        }
+
+        public virtual CharBlockArray Append(string s)
+        {
+            int remain = s.Length;
+            int offset = 0;
+            while (remain > 0)
+            {
+                if (this.current.length == this.blockSize)
+                {
+                    AddBlock();
+                }
+                int toCopy = remain;
+                int remainingInBlock = this.blockSize - this.current.length;
+                if (remainingInBlock < toCopy)
+                {
+                    toCopy = remainingInBlock;
+                }
+                s.CopyTo(offset, this.current.chars, this.current.length, offset + toCopy - offset);
+                offset += toCopy;
+                remain -= toCopy;
+                this.current.length += toCopy;
+            }
+
+            this.length_Renamed += s.Length;
+            return this;
+        }
+
+        public int Length
+        {
+            get
+            {
+                return this.length_Renamed;
+            }
+        }
+
+        public char CharAt(int index)
+        {
+            Block b = blocks[blockIndex(index)];
+            return b.chars[IndexInBlock(index)];
+        }
+
+        public ICharSequence SubSequence(int start, int end)
+        {
+            int remaining = end - start;
+            StringBuilder sb = new StringBuilder(remaining);
+            int blockIdx = blockIndex(start);
+            int indexInBlock = IndexInBlock(start);
+            while (remaining > 0)
+            {
+                Block b = blocks[blockIdx++];
+                int numToAppend = Math.Min(remaining, b.length - indexInBlock);
+                sb.Append(b.chars, indexInBlock, numToAppend);
+                remaining -= numToAppend;
+                indexInBlock = 0; // 2nd+ iterations read from start of the block
+            }
+            return new StringCharSequenceWrapper(sb.ToString());
+        }
+
+
+
+
+        public override string ToString()
+        {
+            StringBuilder sb = new StringBuilder();
+            foreach (Block b in blocks)
+            {
+                sb.Append(b.chars, 0, b.length);
+            }
+            return sb.ToString();
+        }
+
+        internal virtual void Flush(OutputStreamDataOutput @out)
+        {
+            
+            using (var ms = StreamUtils.SerializeToStream(this))
+            {
+                var bytes = ms.ToArray();
+                @out.WriteBytes(bytes, 0, bytes.Length);
+            }
+        }
+
+        public static CharBlockArray Open(BinaryReader @in)
+        {
+            return StreamUtils.DeserializeFromStream(@in) as CharBlockArray;
+        }
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/WriterCache/Cl2oTaxonomyWriterCache.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/WriterCache/Cl2oTaxonomyWriterCache.cs b/Lucene.Net.Facet/Taxonomy/WriterCache/Cl2oTaxonomyWriterCache.cs
new file mode 100644
index 0000000..1b8f0c3
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/WriterCache/Cl2oTaxonomyWriterCache.cs
@@ -0,0 +1,123 @@
+using System.Threading;
+
+namespace Lucene.Net.Facet.Taxonomy.WriterCache
+{
+
+
+
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// <seealso cref="TaxonomyWriterCache"/> using <seealso cref="CompactLabelToOrdinal"/>. Although
+    /// called cache, it maintains in memory all the mappings from category to
+    /// ordinal, relying on that <seealso cref="CompactLabelToOrdinal"/> is an efficient
+    /// mapping for this purpose.
+    /// 
+    /// @lucene.experimental
+    /// </summary>
+    public class Cl2oTaxonomyWriterCache : TaxonomyWriterCache
+    {
+        private const int LockTimeOut = 1000;
+        private readonly ReaderWriterLock @lock = new ReaderWriterLock();
+        private readonly int initialCapcity, numHashArrays;
+        private readonly float loadFactor;
+
+        private volatile CompactLabelToOrdinal cache;
+
+        /// <summary>
+        /// Sole constructor. </summary>
+        public Cl2oTaxonomyWriterCache(int initialCapcity, float loadFactor, int numHashArrays)
+        {
+            this.cache = new CompactLabelToOrdinal(initialCapcity, loadFactor, numHashArrays);
+            this.initialCapcity = initialCapcity;
+            this.numHashArrays = numHashArrays;
+            this.loadFactor = loadFactor;
+        }
+
+        public virtual void Clear()
+        {
+            @lock.AcquireWriterLock(LockTimeOut);
+            try
+            {
+                cache = new CompactLabelToOrdinal(initialCapcity, loadFactor, numHashArrays);
+            }
+            finally
+            {
+                @lock.ReleaseWriterLock();
+            }
+        }
+
+        public virtual void Close()
+        {
+            lock (this)
+            {
+                cache = null;
+            }
+        }
+
+        public virtual bool Full
+        {
+            get
+            {
+                // This cache is never full
+                return false;
+            }
+        }
+
+        public virtual int Get(FacetLabel categoryPath)
+        {
+            @lock.AcquireReaderLock(LockTimeOut);
+            try
+            {
+                return cache.GetOrdinal(categoryPath);
+            }
+            finally
+            {
+                @lock.ReleaseReaderLock();
+            }
+        }
+
+        public virtual bool Put(FacetLabel categoryPath, int ordinal)
+        {
+            @lock.AcquireWriterLock(LockTimeOut);
+            try
+            {
+                cache.AddLabel(categoryPath, ordinal);
+                // Tell the caller we didn't clear part of the cache, so it doesn't
+                // have to flush its on-disk index now
+                return false;
+            }
+            finally
+            {
+                @lock.ReleaseWriterLock();
+            }
+        }
+
+        /// <summary>
+        /// Returns the number of bytes in memory used by this object. </summary>
+        public virtual int MemoryUsage
+        {
+            get
+            {
+                return cache == null ? 0 : cache.MemoryUsage;
+            }
+        }
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/WriterCache/CollisionMap.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/WriterCache/CollisionMap.cs b/Lucene.Net.Facet/Taxonomy/WriterCache/CollisionMap.cs
new file mode 100644
index 0000000..1adb7b3
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/WriterCache/CollisionMap.cs
@@ -0,0 +1,288 @@
+using System;
+using System.Collections.Generic;
+
+namespace Lucene.Net.Facet.Taxonomy.WriterCache
+{
+
+
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// HashMap to store colliding labels. See <seealso cref="CompactLabelToOrdinal"/> for
+    /// details.
+    /// 
+    /// @lucene.experimental
+    /// </summary>
+    public class CollisionMap
+    {
+
+        private int capacity_Renamed;
+        private float loadFactor;
+        private int size_Renamed;
+        private int threshold;
+
+        public class Entry
+        {
+            internal int offset;
+            internal int cid;
+            internal Entry next;
+            internal int hash;
+
+            internal Entry(int offset, int cid, int h, Entry e)
+            {
+                this.offset = offset;
+                this.cid = cid;
+                this.next = e;
+                this.hash = h;
+            }
+        }
+
+        private CharBlockArray labelRepository;
+
+        private Entry[] entries;
+
+        internal CollisionMap(CharBlockArray labelRepository)
+            : this(16 * 1024, 0.75f, labelRepository)
+        {
+        }
+
+        internal CollisionMap(int initialCapacity, CharBlockArray labelRepository)
+            : this(initialCapacity, 0.75f, labelRepository)
+        {
+        }
+
+        private CollisionMap(int initialCapacity, float loadFactor, CharBlockArray labelRepository)
+        {
+            this.labelRepository = labelRepository;
+            this.loadFactor = loadFactor;
+            this.capacity_Renamed = CompactLabelToOrdinal.DetermineCapacity(2, initialCapacity);
+
+            this.entries = new Entry[this.capacity_Renamed];
+            this.threshold = (int)(this.capacity_Renamed * this.loadFactor);
+        }
+
+        /// <summary>
+        /// How many mappings. </summary>
+        public virtual int Size()
+        {
+            return this.size_Renamed;
+        }
+
+        /// <summary>
+        /// How many slots are allocated. </summary>
+        public virtual int capacity()
+        {
+            return this.capacity_Renamed;
+        }
+
+        private void Grow()
+        {
+            int newCapacity = this.capacity_Renamed * 2;
+            Entry[] newEntries = new Entry[newCapacity];
+            Entry[] src = this.entries;
+
+            for (int j = 0; j < src.Length; j++)
+            {
+                Entry e = src[j];
+                if (e != null)
+                {
+                    src[j] = null;
+                    do
+                    {
+                        Entry next = e.next;
+                        int hash = e.hash;
+                        int i = IndexFor(hash, newCapacity);
+                        e.next = newEntries[i];
+                        newEntries[i] = e;
+                        e = next;
+                    } while (e != null);
+                }
+            }
+
+            this.capacity_Renamed = newCapacity;
+            this.entries = newEntries;
+            this.threshold = (int)(this.capacity_Renamed * this.loadFactor);
+        }
+
+        /// <summary>
+        /// Return the mapping, or {@link
+        ///  LabelToOrdinal#INVALID_ORDINAL} if the label isn't
+        ///  recognized. 
+        /// </summary>
+        public virtual int Get(FacetLabel label, int hash)
+        {
+            int bucketIndex = IndexFor(hash, this.capacity_Renamed);
+            Entry e = this.entries[bucketIndex];
+
+            while (e != null && !(hash == e.hash && CategoryPathUtils.EqualsToSerialized(label, labelRepository, e.offset)))
+            {
+                e = e.next;
+            }
+            if (e == null)
+            {
+                return LabelToOrdinal.INVALID_ORDINAL;
+            }
+
+            return e.cid;
+        }
+
+        /// <summary>
+        /// Add another mapping. </summary>
+        public virtual int AddLabel(FacetLabel label, int hash, int cid)
+        {
+            int bucketIndex = IndexFor(hash, this.capacity_Renamed);
+            for (Entry e = this.entries[bucketIndex]; e != null; e = e.next)
+            {
+                if (e.hash == hash && CategoryPathUtils.EqualsToSerialized(label, labelRepository, e.offset))
+                {
+                    return e.cid;
+                }
+            }
+
+            // new string; add to label repository
+            int offset = labelRepository.Length;
+            CategoryPathUtils.Serialize(label, labelRepository);
+            AddEntry(offset, cid, hash, bucketIndex);
+            return cid;
+        }
+
+        /// <summary>
+        /// This method does not check if the same value is already in the map because
+        /// we pass in an char-array offset, so so we now that we're in resize-mode
+        /// here.
+        /// </summary>
+        public virtual void AddLabelOffset(int hash, int offset, int cid)
+        {
+            int bucketIndex = IndexFor(hash, this.capacity_Renamed);
+            AddEntry(offset, cid, hash, bucketIndex);
+        }
+
+        private void AddEntry(int offset, int cid, int hash, int bucketIndex)
+        {
+            Entry e = this.entries[bucketIndex];
+            this.entries[bucketIndex] = new Entry(offset, cid, hash, e);
+            if (this.size_Renamed++ >= this.threshold)
+            {
+                Grow();
+            }
+        }
+
+        internal virtual EntryIterator entryIterator()
+        {
+            return new EntryIterator(this, entries, size_Renamed);
+        }
+
+        /// <summary>
+        /// Returns index for hash code h. 
+        /// </summary>
+        internal static int IndexFor(int h, int length)
+        {
+            return h & (length - 1);
+        }
+
+        /// <summary>
+        /// Returns an estimate of the memory usage of this CollisionMap. </summary>
+        /// <returns> The approximate number of bytes used by this structure. </returns>
+        internal virtual int MemoryUsage
+        {
+            get
+            {
+                int memoryUsage = 0;
+                if (this.entries != null)
+                {
+                    foreach (Entry e in this.entries)
+                    {
+                        if (e != null)
+                        {
+                            memoryUsage += (4 * 4);
+                            for (Entry ee = e.next; ee != null; ee = ee.next)
+                            {
+                                memoryUsage += (4 * 4);
+                            }
+                        }
+                    }
+                }
+                return memoryUsage;
+            }
+        }
+
+        public class EntryIterator 
+        {
+            private readonly CollisionMap outerInstance;
+
+            internal Entry next_Renamed; // next entry to return
+            internal int index; // current slot
+            internal Entry[] ents;
+
+            internal EntryIterator(CollisionMap outerInstance, Entry[] entries, int size)
+            {
+                this.outerInstance = outerInstance;
+                this.ents = entries;
+                Entry[] t = entries;
+                int i = t.Length;
+                Entry n = null;
+                if (size != 0) // advance to first entry
+                {
+                    while (i > 0 && (n = t[--i]) == null)
+                    {
+                        // advance
+                    }
+                }
+                this.next_Renamed = n;
+                this.index = i;
+            }
+
+            public bool HasNext()
+            {
+                return this.next_Renamed != null;
+            }
+
+            public Entry Next()
+            {
+                Entry e = this.next_Renamed;
+                if (e == null)
+                {
+                    throw new IndexOutOfRangeException();
+                }
+
+                Entry n = e.next;
+                Entry[] t = ents;
+                int i = this.index;
+                while (n == null && i > 0)
+                {
+                    n = t[--i];
+                }
+                this.index = i;
+                this.next_Renamed = n;
+                return e;
+            }
+
+            public void Remove()
+            {
+                throw new System.NotSupportedException();
+            }
+
+            public Entry Current()
+            {
+                return ents[index];
+            }
+        }
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/WriterCache/CompactLabelToOrdinal.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/WriterCache/CompactLabelToOrdinal.cs b/Lucene.Net.Facet/Taxonomy/WriterCache/CompactLabelToOrdinal.cs
new file mode 100644
index 0000000..9f73968
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/WriterCache/CompactLabelToOrdinal.cs
@@ -0,0 +1,521 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Lucene.Net.Store;
+
+namespace Lucene.Net.Facet.Taxonomy.WriterCache
+{
+
+    /*
+     * 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.
+     */
+
+
+    /// <summary>
+    /// This is a very efficient LabelToOrdinal implementation that uses a
+    /// CharBlockArray to store all labels and a configurable number of HashArrays to
+    /// reference the labels.
+    /// <para>
+    /// Since the HashArrays don't handle collisions, a <seealso cref="CollisionMap"/> is used
+    /// to store the colliding labels.
+    /// </para>
+    /// <para>
+    /// This data structure grows by adding a new HashArray whenever the number of
+    /// collisions in the <seealso cref="CollisionMap"/> exceeds {@code loadFactor} * 
+    /// <seealso cref="#getMaxOrdinal()"/>. Growing also includes reinserting all colliding
+    /// labels into the HashArrays to possibly reduce the number of collisions.
+    /// 
+    /// For setting the {@code loadFactor} see 
+    /// <seealso cref="#CompactLabelToOrdinal(int, float, int)"/>. 
+    /// 
+    /// </para>
+    /// <para>
+    /// This data structure has a much lower memory footprint (~30%) compared to a
+    /// Java HashMap&lt;String, Integer&gt;. It also only uses a small fraction of objects
+    /// a HashMap would use, thus limiting the GC overhead. Ingestion speed was also
+    /// ~50% faster compared to a HashMap for 3M unique labels.
+    /// 
+    /// @lucene.experimental
+    /// </para>
+    /// </summary>
+    public class CompactLabelToOrdinal : LabelToOrdinal
+    {
+
+        /// <summary>
+        /// Default maximum load factor. </summary>
+        public const float DefaultLoadFactor = 0.15f;
+
+        internal const char TERMINATOR_CHAR = (char)0xffff;
+        private const int COLLISION = -5;
+
+        private HashArray[] hashArrays;
+        private CollisionMap collisionMap;
+        private CharBlockArray labelRepository;
+
+        private int capacity;
+        private int threshold;
+        private float loadFactor;
+
+        /// <summary>
+        /// How many labels. </summary>
+        public virtual int SizeOfMap()
+        {
+            return this.collisionMap.Size();
+        }
+
+        private CompactLabelToOrdinal()
+        {
+        }
+
+        /// <summary>
+        /// Sole constructor. </summary>
+        public CompactLabelToOrdinal(int initialCapacity, float loadFactor, int numHashArrays)
+        {
+
+            this.hashArrays = new HashArray[numHashArrays];
+
+            this.capacity = DetermineCapacity((int)Math.Pow(2, numHashArrays), initialCapacity);
+            Init();
+            this.collisionMap = new CollisionMap(this.labelRepository);
+
+            this.counter = 0;
+            this.loadFactor = loadFactor;
+
+            this.threshold = (int)(this.loadFactor * this.capacity);
+        }
+
+        internal static int DetermineCapacity(int minCapacity, int initialCapacity)
+        {
+            int capacity = minCapacity;
+            while (capacity < initialCapacity)
+            {
+                capacity <<= 1;
+            }
+            return capacity;
+        }
+
+        private void Init()
+        {
+            labelRepository = new CharBlockArray();
+            CategoryPathUtils.Serialize(new FacetLabel(), labelRepository);
+
+            int c = this.capacity;
+            for (int i = 0; i < this.hashArrays.Length; i++)
+            {
+                this.hashArrays[i] = new HashArray(c);
+                c /= 2;
+            }
+        }
+
+        public override void AddLabel(FacetLabel label, int ordinal)
+        {
+            if (collisionMap.Size() > threshold)
+            {
+                Grow();
+            }
+
+            int hash = CompactLabelToOrdinal.StringHashCode(label);
+            for (int i = 0; i < this.hashArrays.Length; i++)
+            {
+                if (AddLabel(this.hashArrays[i], label, hash, ordinal))
+                {
+                    return;
+                }
+            }
+
+            int prevVal = collisionMap.AddLabel(label, hash, ordinal);
+            if (prevVal != ordinal)
+            {
+                throw new System.ArgumentException("Label already exists: " + label + " prev ordinal " + prevVal);
+            }
+        }
+
+        public override int GetOrdinal(FacetLabel label)
+        {
+            if (label == null)
+            {
+                return LabelToOrdinal.INVALID_ORDINAL;
+            }
+
+            int hash = CompactLabelToOrdinal.StringHashCode(label);
+            for (int i = 0; i < this.hashArrays.Length; i++)
+            {
+                int ord = GetOrdinal(this.hashArrays[i], label, hash);
+                if (ord != COLLISION)
+                {
+                    return ord;
+                }
+            }
+
+            return this.collisionMap.Get(label, hash);
+        }
+
+        private void Grow()
+        {
+            HashArray temp = this.hashArrays[this.hashArrays.Length - 1];
+
+            for (int i = this.hashArrays.Length - 1; i > 0; i--)
+            {
+                this.hashArrays[i] = this.hashArrays[i - 1];
+            }
+
+            this.capacity *= 2;
+            this.hashArrays[0] = new HashArray(this.capacity);
+
+            for (int i = 1; i < this.hashArrays.Length; i++)
+            {
+                int[] sourceOffsetArray = this.hashArrays[i].offsets;
+                int[] sourceCidsArray = this.hashArrays[i].cids;
+
+                for (int k = 0; k < sourceOffsetArray.Length; k++)
+                {
+
+                    for (int j = 0; j < i && sourceOffsetArray[k] != 0; j++)
+                    {
+                        int[] targetOffsetArray = this.hashArrays[j].offsets;
+                        int[] targetCidsArray = this.hashArrays[j].cids;
+
+                        int newIndex = IndexFor(StringHashCode(this.labelRepository, sourceOffsetArray[k]), targetOffsetArray.Length);
+                        if (targetOffsetArray[newIndex] == 0)
+                        {
+                            targetOffsetArray[newIndex] = sourceOffsetArray[k];
+                            targetCidsArray[newIndex] = sourceCidsArray[k];
+                            sourceOffsetArray[k] = 0;
+                        }
+                    }
+                }
+            }
+
+            for (int i = 0; i < temp.offsets.Length; i++)
+            {
+                int offset = temp.offsets[i];
+                if (offset > 0)
+                {
+                    int hash = StringHashCode(this.labelRepository, offset);
+                    AddLabelOffset(hash, temp.cids[i], offset);
+                }
+            }
+
+            CollisionMap oldCollisionMap = this.collisionMap;
+            this.collisionMap = new CollisionMap(oldCollisionMap.capacity(), this.labelRepository);
+            this.threshold = (int)(this.capacity * this.loadFactor);
+
+            var it = oldCollisionMap.entryIterator();
+            while (it.HasNext())
+            {
+                var e = it.Current();
+                AddLabelOffset(StringHashCode(this.labelRepository, e.offset), e.cid, e.offset);
+            }
+        }
+
+        private bool AddLabel(HashArray a, FacetLabel label, int hash, int ordinal)
+        {
+            int index = CompactLabelToOrdinal.IndexFor(hash, a.offsets.Length);
+            int offset = a.offsets[index];
+
+            if (offset == 0)
+            {
+                a.offsets[index] = this.labelRepository.Length;
+                CategoryPathUtils.Serialize(label, labelRepository);
+                a.cids[index] = ordinal;
+                return true;
+            }
+
+            return false;
+        }
+
+        private void AddLabelOffset(int hash, int cid, int knownOffset)
+        {
+            for (int i = 0; i < this.hashArrays.Length; i++)
+            {
+                if (AddLabelOffsetToHashArray(this.hashArrays[i], hash, cid, knownOffset))
+                {
+                    return;
+                }
+            }
+
+            this.collisionMap.AddLabelOffset(hash, knownOffset, cid);
+
+            if (this.collisionMap.Size() > this.threshold)
+            {
+                Grow();
+            }
+        }
+
+        private bool AddLabelOffsetToHashArray(HashArray a, int hash, int ordinal, int knownOffset)
+        {
+
+            int index = CompactLabelToOrdinal.IndexFor(hash, a.offsets.Length);
+            int offset = a.offsets[index];
+
+            if (offset == 0)
+            {
+                a.offsets[index] = knownOffset;
+                a.cids[index] = ordinal;
+                return true;
+            }
+
+            return false;
+        }
+
+        private int GetOrdinal(HashArray a, FacetLabel label, int hash)
+        {
+            if (label == null)
+            {
+                return LabelToOrdinal.INVALID_ORDINAL;
+            }
+
+            int index = IndexFor(hash, a.offsets.Length);
+            int offset = a.offsets[index];
+            if (offset == 0)
+            {
+                return LabelToOrdinal.INVALID_ORDINAL;
+            }
+
+            if (CategoryPathUtils.EqualsToSerialized(label, labelRepository, offset))
+            {
+                return a.cids[index];
+            }
+
+            return COLLISION;
+        }
+
+        /// <summary>
+        /// Returns index for hash code h. </summary>
+        internal static int IndexFor(int h, int length)
+        {
+            return h & (length - 1);
+        }
+
+        // static int stringHashCode(String label) {
+        // int len = label.length();
+        // int hash = 0;
+        // int i;
+        // for (i = 0; i < len; ++i)
+        // hash = 33 * hash + label.charAt(i);
+        //
+        // hash = hash ^ ((hash >>> 20) ^ (hash >>> 12));
+        // hash = hash ^ (hash >>> 7) ^ (hash >>> 4);
+        //
+        // return hash;
+        //
+        // }
+
+        internal static int StringHashCode(FacetLabel label)
+        {
+            int hash = label.GetHashCode();
+
+            hash = hash ^ (((int)((uint)hash >> 20)) ^ ((int)((uint)hash >> 12)));
+            hash = hash ^ ((int)((uint)hash >> 7)) ^ ((int)((uint)hash >> 4));
+
+            return hash;
+
+        }
+
+        internal static int StringHashCode(CharBlockArray labelRepository, int offset)
+        {
+            int hash = CategoryPathUtils.HashCodeOfSerialized(labelRepository, offset);
+            hash = hash ^ (((int)((uint)hash >> 20)) ^ ((int)((uint)hash >> 12)));
+            hash = hash ^ ((int)((uint)hash >> 7)) ^ ((int)((uint)hash >> 4));
+            return hash;
+        }
+
+        // public static boolean equals(CharSequence label, CharBlockArray array,
+        // int offset) {
+        // // CONTINUE HERE
+        // int len = label.length();
+        // int bi = array.blockIndex(offset);
+        // CharBlockArray.Block b = array.blocks.get(bi);
+        // int index = array.indexInBlock(offset);
+        //
+        // for (int i = 0; i < len; i++) {
+        // if (label.charAt(i) != b.chars[index]) {
+        // return false;
+        // }
+        // index++;
+        // if (index == b.length) {
+        // b = array.blocks.get(++bi);
+        // index = 0;
+        // }
+        // }
+        //
+        // return b.chars[index] == TerminatorChar;
+        // }
+
+        /// <summary>
+        /// Returns an estimate of the amount of memory used by this table. Called only in
+        /// this package. Memory is consumed mainly by three structures: the hash arrays,
+        /// label repository and collision map.
+        /// </summary>
+        internal virtual int MemoryUsage
+        {
+            get
+            {
+                int memoryUsage = 0;
+                if (this.hashArrays != null)
+                {
+                    // HashArray capacity is instance-specific.
+                    foreach (HashArray ha in this.hashArrays)
+                    {
+                        // Each has 2 capacity-length arrays of ints.
+                        memoryUsage += (ha.capacity * 2 * 4) + 4;
+                    }
+                }
+                if (this.labelRepository != null)
+                {
+                    // All blocks are the same size.
+                    int blockSize = this.labelRepository.blockSize;
+                    // Each block has room for blockSize UTF-16 chars.
+                    int actualBlockSize = (blockSize * 2) + 4;
+                    memoryUsage += this.labelRepository.blocks.Count * actualBlockSize;
+                    memoryUsage += 8; // Two int values for array as a whole.
+                }
+                if (this.collisionMap != null)
+                {
+                    memoryUsage += this.collisionMap.MemoryUsage;
+                }
+                return memoryUsage;
+            }
+        }
+
+        /// <summary>
+        /// Opens the file and reloads the CompactLabelToOrdinal. The file it expects
+        /// is generated from the <seealso cref="#flush(File)"/> command.
+        /// </summary>
+        internal static CompactLabelToOrdinal Open(string file, float loadFactor, int numHashArrays)
+        {
+            /// <summary>
+            /// Part of the file is the labelRepository, which needs to be rehashed
+            /// and label offsets re-added to the object. I am unsure as to why we
+            /// can't just store these off in the file as well, but in keeping with
+            /// the spirit of the original code, I did it this way. (ssuppe)
+            /// </summary>
+            CompactLabelToOrdinal l2o = new CompactLabelToOrdinal();
+            l2o.loadFactor = loadFactor;
+            l2o.hashArrays = new HashArray[numHashArrays];
+
+            BinaryReader dis = null;
+            try
+            {
+                dis = new BinaryReader(new FileStream(file,FileMode.Open,FileAccess.Read));
+
+                // TaxiReader needs to load the "counter" or occupancy (L2O) to know
+                // the next unique facet. we used to load the delimiter too, but
+                // never used it.
+                l2o.counter = dis.ReadInt32();
+
+                l2o.capacity = DetermineCapacity((int)Math.Pow(2, l2o.hashArrays.Length), l2o.counter);
+                l2o.Init();
+
+                // now read the chars
+                l2o.labelRepository = CharBlockArray.Open(dis);
+
+                l2o.collisionMap = new CollisionMap(l2o.labelRepository);
+
+                // Calculate hash on the fly based on how CategoryPath hashes
+                // itself. Maybe in the future we can call some static based methods
+                // in CategoryPath so that this doesn't break again? I don't like
+                // having code in two different places...
+                int cid = 0;
+                // Skip the initial offset, it's the CategoryPath(0,0), which isn't
+                // a hashed value.
+                int offset = 1;
+                int lastStartOffset = offset;
+                // This loop really relies on a well-formed input (assumes pretty blindly
+                // that array offsets will work).  Since the initial file is machine 
+                // generated, I think this should be OK.
+                while (offset < l2o.labelRepository.Length)
+                {
+                    // identical code to CategoryPath.hashFromSerialized. since we need to
+                    // advance offset, we cannot call the method directly. perhaps if we
+                    // could pass a mutable Integer or something...
+                    int length = (short)l2o.labelRepository.CharAt(offset++);
+                    int hash = length;
+                    if (length != 0)
+                    {
+                        for (int i = 0; i < length; i++)
+                        {
+                            int len = (short)l2o.labelRepository.CharAt(offset++);
+                            hash = hash * 31 + l2o.labelRepository.SubSequence(offset, offset + len).GetHashCode();
+                            offset += len;
+                        }
+                    }
+                    // Now that we've hashed the components of the label, do the
+                    // final part of the hash algorithm.
+                    hash = hash ^ (((int)((uint)hash >> 20)) ^ ((int)((uint)hash >> 12)));
+                    hash = hash ^ ((int)((uint)hash >> 7)) ^ ((int)((uint)hash >> 4));
+                    // Add the label, and let's keep going
+                    l2o.AddLabelOffset(hash, cid, lastStartOffset);
+                    cid++;
+                    lastStartOffset = offset;
+                }
+
+            }
+            catch (DllNotFoundException)
+            {
+                throw new IOException("Invalid file format. Cannot deserialize.");
+            }
+            finally
+            {
+                if (dis != null)
+                {
+                    dis.Dispose();
+                }
+            }
+
+            l2o.threshold = (int)(l2o.loadFactor * l2o.capacity);
+            return l2o;
+
+        }
+
+        internal virtual void Flush(Stream stream)
+        {
+
+            OutputStreamDataOutput dos = new OutputStreamDataOutput(stream);
+
+            try
+            {
+                dos.WriteInt(this.counter);
+
+                // write the labelRepository
+                this.labelRepository.Flush(dos);
+                // Closes the data output stream
+                dos.Dispose();
+
+            }
+            finally
+            {
+                dos.Dispose();
+            }
+        }
+
+        private sealed class HashArray
+        {
+            internal int[] offsets;
+            internal int[] cids;
+
+            internal int capacity;
+
+            internal HashArray(int c)
+            {
+                this.capacity = c;
+                this.offsets = new int[this.capacity];
+                this.cids = new int[this.capacity];
+            }
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/WriterCache/LabelToOrdinal.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/WriterCache/LabelToOrdinal.cs b/Lucene.Net.Facet/Taxonomy/WriterCache/LabelToOrdinal.cs
new file mode 100644
index 0000000..495aa9b
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/WriterCache/LabelToOrdinal.cs
@@ -0,0 +1,83 @@
+namespace Lucene.Net.Facet.Taxonomy.WriterCache
+{
+
+	/*
+	 * 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.
+	 */
+
+	/// <summary>
+	/// Abstract class for storing Label->Ordinal mappings in a taxonomy. 
+	/// 
+	/// @lucene.experimental
+	/// </summary>
+	public abstract class LabelToOrdinal
+	{
+
+	  /// <summary>
+	  /// How many ordinals we've seen. </summary>
+	  protected internal int counter;
+
+	  /// <summary>
+	  /// Returned by <seealso cref="#getOrdinal"/> when the label isn't
+	  ///  recognized. 
+	  /// </summary>
+	  public const int INVALID_ORDINAL = -2;
+
+	  /// <summary>
+	  /// Default constructor. </summary>
+	  public LabelToOrdinal()
+	  {
+	  }
+
+	  /// <summary>
+	  /// return the maximal Ordinal assigned so far
+	  /// </summary>
+	  public virtual int MaxOrdinal
+	  {
+		  get
+		  {
+			return this.counter;
+		  }
+	  }
+
+	  /// <summary>
+	  /// Returns the next unassigned ordinal. The default behavior of this method
+	  /// is to simply increment a counter.
+	  /// </summary>
+	  public virtual int NextOrdinal
+	  {
+		  get
+		  {
+			return this.counter++;
+		  }
+	  }
+
+	  /// <summary>
+	  /// Adds a new label if its not yet in the table.
+	  /// Throws an <seealso cref="IllegalArgumentException"/> if the same label with
+	  /// a different ordinal was previoulsy added to this table.
+	  /// </summary>
+	  public abstract void AddLabel(FacetLabel label, int ordinal);
+
+	  /// <summary>
+	  /// Returns the ordinal assigned to the given label, 
+	  /// or <seealso cref="#INVALID_ORDINAL"/> if the label cannot be found in this table.
+	  /// </summary>
+	  public abstract int GetOrdinal(FacetLabel label);
+
+	}
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/WriterCache/LruTaxonomyWriterCache.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/WriterCache/LruTaxonomyWriterCache.cs b/Lucene.Net.Facet/Taxonomy/WriterCache/LruTaxonomyWriterCache.cs
new file mode 100644
index 0000000..74bf396
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/WriterCache/LruTaxonomyWriterCache.cs
@@ -0,0 +1,149 @@
+namespace Lucene.Net.Facet.Taxonomy.WriterCache
+{
+
+
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// LRU <seealso cref="TaxonomyWriterCache"/> - good choice for huge taxonomies.
+    /// 
+    /// @lucene.experimental
+    /// </summary>
+    public class LruTaxonomyWriterCache : TaxonomyWriterCache
+    {
+
+        /// <summary>
+        /// Determines cache type.
+        /// For guaranteed correctness - not relying on no-collisions in the hash
+        /// function, LRU_STRING should be used.
+        /// </summary>
+        public enum LRUType
+        {
+            /// <summary>
+            /// Use the label's hash as the key; this can lead to
+            ///  silent conflicts! 
+            /// </summary>
+            LRU_HASHED,
+
+            /// <summary>
+            /// Use the label as the hash key; this is always
+            ///  correct but will usually use more RAM. 
+            /// </summary>
+            LRU_STRING
+        }
+
+        private NameIntCacheLRU cache;
+
+        /// <summary>
+        /// Creates this with <seealso cref="LRUType#LRU_HASHED"/> method. </summary>
+        public LruTaxonomyWriterCache(int cacheSize)
+            : this(cacheSize, LRUType.LRU_HASHED)
+        {
+            // TODO (Facet): choose between NameHashIntCacheLRU and NameIntCacheLRU.
+            // For guaranteed correctness - not relying on no-collisions in the hash
+            // function, NameIntCacheLRU should be used:
+            // On the other hand, NameHashIntCacheLRU takes less RAM but if there
+            // are collisions (which we never found) two different paths would be
+            // mapped to the same ordinal...
+        }
+
+        /// <summary>
+        /// Creates this with the specified method. </summary>
+        public LruTaxonomyWriterCache(int cacheSize, LRUType lruType)
+        {
+            // TODO (Facet): choose between NameHashIntCacheLRU and NameIntCacheLRU.
+            // For guaranteed correctness - not relying on no-collisions in the hash
+            // function, NameIntCacheLRU should be used:
+            // On the other hand, NameHashIntCacheLRU takes less RAM but if there
+            // are collisions (which we never found) two different paths would be
+            // mapped to the same ordinal...
+            if (lruType == LRUType.LRU_HASHED)
+            {
+                this.cache = new NameHashIntCacheLRU(cacheSize);
+            }
+            else
+            {
+                this.cache = new NameIntCacheLRU(cacheSize);
+            }
+        }
+
+        public virtual bool Full
+        {
+            get
+            {
+                lock (this)
+                {
+                    return cache.Size == cache.MaxSize;
+                }
+            }
+        }
+
+        public virtual void Clear()
+        {
+            lock (this)
+            {
+                cache.Clear();
+            }
+        }
+
+        public virtual void Close()
+        {
+            lock (this)
+            {
+                cache.Clear();
+                cache = null;
+            }
+        }
+
+        public virtual int Get(FacetLabel categoryPath)
+        {
+            lock (this)
+            {
+                int? res = cache.Get(categoryPath);
+                if (res == null)
+                {
+                    return -1;
+                }
+
+                return (int)res;
+            }
+        }
+
+        public virtual bool Put(FacetLabel categoryPath, int ordinal)
+        {
+            lock (this)
+            {
+                bool ret = cache.Put(categoryPath, new int?(ordinal));
+                // If the cache is full, we need to clear one or more old entries
+                // from the cache. However, if we delete from the cache a recent
+                // addition that isn't yet in our reader, for this entry to be
+                // visible to us we need to make sure that the changes have been
+                // committed and we reopen the reader. Because this is a slow
+                // operation, we don't delete entries one-by-one but rather in bulk
+                // (put() removes the 2/3rd oldest entries).
+                if (ret)
+                {
+                    cache.MakeRoomLRU();
+                }
+                return ret;
+            }
+        }
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/WriterCache/NameHashIntCacheLRU.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/WriterCache/NameHashIntCacheLRU.cs b/Lucene.Net.Facet/Taxonomy/WriterCache/NameHashIntCacheLRU.cs
new file mode 100644
index 0000000..cf135ea
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/WriterCache/NameHashIntCacheLRU.cs
@@ -0,0 +1,49 @@
+namespace Lucene.Net.Facet.Taxonomy.WriterCache
+{
+
+	/*
+	 * 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.
+	 */
+
+	/// <summary>
+	/// An an LRU cache of mapping from name to int.
+	/// Used to cache Ordinals of category paths.
+	/// It uses as key, hash of the path instead of the path.
+	/// This way the cache takes less RAM, but correctness depends on
+	/// assuming no collisions. 
+	/// 
+	/// @lucene.experimental
+	/// </summary>
+	public class NameHashIntCacheLRU : NameIntCacheLRU
+	{
+
+	  internal NameHashIntCacheLRU(int maxCacheSize) : base(maxCacheSize)
+	  {
+	  }
+
+	  internal override object Key(FacetLabel name)
+	  {
+		return new long?(name.LongHashCode());
+	  }
+
+	  internal override object Key(FacetLabel name, int prefixLen)
+	  {
+		return new long?(name.Subpath(prefixLen).LongHashCode());
+	  }
+
+	}
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/WriterCache/NameIntCacheLRU.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/WriterCache/NameIntCacheLRU.cs b/Lucene.Net.Facet/Taxonomy/WriterCache/NameIntCacheLRU.cs
new file mode 100644
index 0000000..cd01527
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/WriterCache/NameIntCacheLRU.cs
@@ -0,0 +1,167 @@
+using System.Collections.Generic;
+
+namespace Lucene.Net.Facet.Taxonomy.WriterCache
+{
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// An an LRU cache of mapping from name to int.
+    /// Used to cache Ordinals of category paths.
+    /// 
+    /// @lucene.experimental
+    /// </summary>
+    // Note: Nothing in this class is synchronized. The caller is assumed to be
+    // synchronized so that no two methods of this class are called concurrently.
+    public class NameIntCacheLRU
+    {
+
+        private Dictionary<object, int?> cache;
+        internal long nMisses = 0; // for debug
+        internal long nHits = 0; // for debug
+        private int maxCacheSize;
+
+        internal NameIntCacheLRU(int maxCacheSize)
+        {
+            this.maxCacheSize = maxCacheSize;
+            CreateCache(maxCacheSize);
+        }
+
+        /// <summary>
+        /// Maximum number of cache entries before eviction. </summary>
+        public virtual int MaxSize
+        {
+            get
+            {
+                return maxCacheSize;
+            }
+        }
+
+        /// <summary>
+        /// Number of entries currently in the cache. </summary>
+        public virtual int Size
+        {
+            get
+            {
+                return cache.Count;
+            }
+        }
+
+        private void CreateCache(int maxSize)
+	  {
+        //if (maxSize < int.MaxValue)
+        //{
+        //    cache = new LRUHashMap<object,int?>(1000,true); //for LRU
+        //}
+        //else
+		{
+		  cache = new Dictionary<object, int?>(1000); //no need for LRU
+		}
+	  }
+
+        internal virtual int? Get(FacetLabel name)
+        {
+            int? res = cache[Key(name)];
+            if (res == null)
+            {
+                nMisses++;
+            }
+            else
+            {
+                nHits++;
+            }
+            return res;
+        }
+
+        /// <summary>
+        /// Subclasses can override this to provide caching by e.g. hash of the string. </summary>
+        internal virtual object Key(FacetLabel name)
+        {
+            return name;
+        }
+
+        internal virtual object Key(FacetLabel name, int prefixLen)
+        {
+            return name.Subpath(prefixLen);
+        }
+
+        /// <summary>
+        /// Add a new value to cache.
+        /// Return true if cache became full and some room need to be made. 
+        /// </summary>
+        internal virtual bool Put(FacetLabel name, int? val)
+        {
+            cache[Key(name)] = val;
+            return CacheFull;
+        }
+
+        internal virtual bool Put(FacetLabel name, int prefixLen, int? val)
+        {
+            cache[Key(name, prefixLen)] = val;
+            return CacheFull;
+        }
+
+        private bool CacheFull
+        {
+            get
+            {
+                return cache.Count > maxCacheSize;
+            }
+        }
+
+        internal virtual void Clear()
+        {
+            cache.Clear();
+        }
+
+        internal virtual string Stats()
+        {
+            return "#miss=" + nMisses + " #hit=" + nHits;
+        }
+
+        /// <summary>
+        /// If cache is full remove least recently used entries from cache. Return true
+        /// if anything was removed, false otherwise.
+        /// 
+        /// See comment in DirectoryTaxonomyWriter.addToCache(CategoryPath, int) for an
+        /// explanation why we clean 2/3rds of the cache, and not just one entry.
+        /// </summary>
+        internal virtual bool MakeRoomLRU()
+        {
+            if (!CacheFull)
+            {
+                return false;
+            }
+            int n = cache.Count - (2 * maxCacheSize) / 3;
+            if (n <= 0)
+            {
+                return false;
+            }
+            IEnumerator<object> it = cache.Keys.GetEnumerator();
+            int i = 0;
+            
+            while (i < n && it.MoveNext())
+            {
+                cache.Remove(it.Current);
+                i++;
+            }
+            return true;
+        }
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/Taxonomy/WriterCache/TaxonomyWriterCache.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/Taxonomy/WriterCache/TaxonomyWriterCache.cs b/Lucene.Net.Facet/Taxonomy/WriterCache/TaxonomyWriterCache.cs
new file mode 100644
index 0000000..a20dcd6
--- /dev/null
+++ b/Lucene.Net.Facet/Taxonomy/WriterCache/TaxonomyWriterCache.cs
@@ -0,0 +1,105 @@
+namespace Lucene.Net.Facet.Taxonomy.WriterCache
+{
+
+    using DirectoryTaxonomyWriter = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter;
+
+    /*
+     * 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.
+     */
+
+    /// <summary>
+    /// TaxonomyWriterCache is a relatively simple interface for a cache of
+    /// category->ordinal mappings, used in TaxonomyWriter implementations (such as
+    /// <seealso cref="DirectoryTaxonomyWriter"/>).
+    /// <para>
+    /// It basically has put() methods for adding a mapping, and get() for looking a
+    /// mapping up the cache. The cache does <B>not</B> guarantee to hold everything
+    /// that has been put into it, and might in fact selectively delete some of the
+    /// mappings (e.g., the ones least recently used). This means that if get()
+    /// returns a negative response, it does not necessarily mean that the category
+    /// doesn't exist - just that it is not in the cache. The caller can only infer
+    /// that the category doesn't exist if it knows the cache to be complete (because
+    /// all the categories were loaded into the cache, and since then no put()
+    /// returned true).
+    /// </para>
+    /// <para>
+    /// However, if it does so, it should clear out large parts of the cache at once,
+    /// because the user will typically need to work hard to recover from every cache
+    /// cleanup (see <seealso cref="#put(FacetLabel, int)"/>'s return value).
+    /// </para>
+    /// <para>
+    /// <b>NOTE:</b> the cache may be accessed concurrently by multiple threads,
+    /// therefore cache implementations should take this into consideration.
+    /// 
+    /// @lucene.experimental
+    /// </para>
+    /// </summary>
+    public interface TaxonomyWriterCache
+    {
+
+        /// <summary>
+        /// Let go of whatever resources the cache is holding. After a close(),
+        /// this object can no longer be used.
+        /// </summary>
+        void Close();
+
+        /// <summary>
+        /// Lookup a category in the cache, returning its ordinal, or a negative
+        /// number if the category is not in the cache.
+        /// <P>
+        /// It is up to the caller to remember what a negative response means:
+        /// If the caller knows the cache is <I>complete</I> (it was initially
+        /// fed with all the categories, and since then put() never returned true)
+        /// it means the category does not exist. Otherwise, the category might
+        /// still exist, but just be missing from the cache.
+        /// </summary>
+        int Get(FacetLabel categoryPath);
+
+        /// <summary>
+        /// Add a category to the cache, with the given ordinal as the value.
+        /// <P>
+        /// If the implementation keeps only a partial cache (e.g., an LRU cache)
+        /// and finds that its cache is full, it should clear up part of the cache
+        /// and return <code>true</code>. Otherwise, it should return
+        /// <code>false</code>.
+        /// <P>
+        /// The reason why the caller needs to know if part of the cache was
+        /// cleared is that in that case it will have to commit its on-disk index
+        /// (so that all the latest category additions can be searched on disk, if
+        /// we can't rely on the cache to contain them).
+        /// <P>
+        /// Ordinals should be non-negative. Currently there is no defined way to
+        /// specify that a cache should remember a category does NOT exist.
+        /// It doesn't really matter, because normally the next thing we do after
+        /// finding that a category does not exist is to add it.
+        /// </summary>
+        bool Put(FacetLabel categoryPath, int ordinal);
+
+        /// <summary>
+        /// Returns true if the cache is full, such that the next <seealso cref="#put"/> will
+        /// evict entries from it, false otherwise.
+        /// </summary>
+        bool Full { get; }
+
+        /// <summary>
+        /// Clears the content of the cache. Unlike <seealso cref="#close()"/>, the caller can
+        /// assume that the cache is still operable after this method returns.
+        /// </summary>
+        void Clear();
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/Lucene.Net.Facet/TopOrdAndFloatQueue.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Facet/TopOrdAndFloatQueue.cs b/Lucene.Net.Facet/TopOrdAndFloatQueue.cs
new file mode 100644
index 0000000..6430624
--- /dev/null
+++ b/Lucene.Net.Facet/TopOrdAndFloatQueue.cs
@@ -0,0 +1,73 @@
+namespace Lucene.Net.Facet
+{
+
+	/*
+	 * 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 Lucene.Net.Util;
+
+	/// <summary>
+	/// Keeps highest results, first by largest float value,
+	///  then tie break by smallest ord. 
+	/// </summary>
+	public class TopOrdAndFloatQueue : PriorityQueue<TopOrdAndFloatQueue.OrdAndValue>
+	{
+
+	  /// <summary>
+	  /// Holds a single entry. </summary>
+	  public sealed class OrdAndValue
+	  {
+
+		/// <summary>
+		/// Ordinal of the entry. </summary>
+		public int ord;
+
+		/// <summary>
+		/// Value associated with the ordinal. </summary>
+		public float value;
+
+		/// <summary>
+		/// Default constructor. </summary>
+		public OrdAndValue()
+		{
+		}
+	  }
+
+	  /// <summary>
+	  /// Sole constructor. </summary>
+	  public TopOrdAndFloatQueue(int topN) : base(topN, false)
+	  {
+	  }
+
+	  public override bool LessThan(OrdAndValue a, OrdAndValue b)
+	  {
+		if (a.value < b.value)
+		{
+		  return true;
+		}
+		else if (a.value > b.value)
+		{
+		  return false;
+		}
+		else
+		{
+		  return a.ord > b.ord;
+		}
+	  }
+	}
+
+}
\ No newline at end of file


[09/26] lucenenet git commit: first commit of facet porting, failing tests will be fixed in next commits.

Posted by sy...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestSearcherTaxonomyManager.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestSearcherTaxonomyManager.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestSearcherTaxonomyManager.cs
new file mode 100644
index 0000000..2d90fe4
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestSearcherTaxonomyManager.cs
@@ -0,0 +1,398 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using Apache.NMS;
+using Lucene.Net.Support;
+using NUnit.Framework;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * 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 MockAnalyzer = Lucene.Net.Analysis.MockAnalyzer;
+    using Document = Lucene.Net.Documents.Document;
+    using SearcherAndTaxonomy = Lucene.Net.Facet.Taxonomy.SearcherTaxonomyManager.SearcherAndTaxonomy;
+    using DirectoryTaxonomyWriter = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter;
+    using IndexWriter = Lucene.Net.Index.IndexWriter;
+    using IndexWriterConfig = Lucene.Net.Index.IndexWriterConfig;
+    using TieredMergePolicy = Lucene.Net.Index.TieredMergePolicy;
+    using MatchAllDocsQuery = Lucene.Net.Search.MatchAllDocsQuery;
+    using Lucene.Net.Search;
+    using Directory = Lucene.Net.Store.Directory;
+    using IOUtils = Lucene.Net.Util.IOUtils;
+    using TestUtil = Lucene.Net.Util.TestUtil;
+    [TestFixture]
+    public class TestSearcherTaxonomyManager : FacetTestCase
+    {
+
+        private class IndexerThread : ThreadClass
+        {
+
+            internal IndexWriter w;
+            internal FacetsConfig config;
+            internal TaxonomyWriter tw;
+            internal ReferenceManager<SearcherAndTaxonomy> mgr;
+            internal int ordLimit;
+            internal AtomicBoolean stop;
+
+            public IndexerThread(IndexWriter w, FacetsConfig config, TaxonomyWriter tw, ReferenceManager<SearcherAndTaxonomy> mgr, int ordLimit, AtomicBoolean stop)
+            {
+                this.w = w;
+                this.config = config;
+                this.tw = tw;
+                this.mgr = mgr;
+                this.ordLimit = ordLimit;
+                this.stop = stop;
+            }
+
+            public override void Run()
+            {
+                try
+                {
+                    var seen = new HashSet<string>();
+                    IList<string> paths = new List<string>();
+                    while (true)
+                    {
+                        Document doc = new Document();
+                        int numPaths = TestUtil.NextInt(Random(), 1, 5);
+                        for (int i = 0; i < numPaths; i++)
+                        {
+                            string path;
+                            if (paths.Count > 0 && Random().Next(5) != 4)
+                            {
+                                // Use previous path
+                                path = paths[Random().Next(paths.Count)];
+                            }
+                            else
+                            {
+                                // Create new path
+                                path = null;
+                                while (true)
+                                {
+                                    path = TestUtil.RandomRealisticUnicodeString(Random());
+                                    if (path.Length != 0 && !seen.Contains(path))
+                                    {
+                                        seen.Add(path);
+                                        paths.Add(path);
+                                        break;
+                                    }
+                                }
+                            }
+                            doc.Add(new FacetField("field", path));
+                        }
+                        try
+                        {
+                            w.AddDocument(config.Build(tw, doc));
+                            if (mgr != null && Random().NextDouble() < 0.02)
+                            {
+                                w.Commit();
+                                tw.Commit();
+                                mgr.MaybeRefresh();
+                            }
+                        }
+                        catch (IOException ioe)
+                        {
+                            throw new Exception(ioe.Message, ioe);
+                        }
+
+                        if (VERBOSE)
+                        {
+                            Console.WriteLine("TW size=" + tw.Size + " vs " + ordLimit);
+                        }
+
+                        if (tw.Size >= ordLimit)
+                        {
+                            break;
+                        }
+                    }
+                }
+                finally
+                {
+                    stop.Set(true);
+                }
+            }
+
+        }
+
+        [Test]
+        public virtual void TestNrt()
+        {
+            Store.Directory dir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+            IndexWriterConfig iwc = NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random()));
+            // Don't allow tiny maxBufferedDocs; it can make this
+            // test too slow:
+            iwc.SetMaxBufferedDocs(Math.Max(500, iwc.MaxBufferedDocs));
+
+            // MockRandom/AlcololicMergePolicy are too slow:
+            TieredMergePolicy tmp = new TieredMergePolicy();
+            tmp.FloorSegmentMB = .001;
+            iwc.SetMergePolicy(tmp);
+            IndexWriter w = new IndexWriter(dir, iwc);
+            var tw = new DirectoryTaxonomyWriter(taxoDir);
+            FacetsConfig config = new FacetsConfig();
+            config.SetMultiValued("field", true);
+            AtomicBoolean stop = new AtomicBoolean();
+
+            // How many unique facets to index before stopping:
+            int ordLimit = TEST_NIGHTLY ? 100000 : 6000;
+
+            var indexer = new IndexerThread(w, config, tw, null, ordLimit, stop);
+
+            var mgr = new SearcherTaxonomyManager(w, true, null, tw);
+
+            var reopener = new ThreadAnonymousInnerClassHelper(this, stop, mgr);
+
+            reopener.Name = "reopener";
+            reopener.Start();
+
+            indexer.Name = "indexer";
+            indexer.Start();
+
+            try
+            {
+                while (!stop.Get())
+                {
+                    SearcherAndTaxonomy pair = mgr.Acquire();
+                    try
+                    {
+                        //System.out.println("search maxOrd=" + pair.taxonomyReader.getSize());
+                        FacetsCollector sfc = new FacetsCollector();
+                        pair.searcher.Search(new MatchAllDocsQuery(), sfc);
+                        Facets facets = GetTaxonomyFacetCounts(pair.taxonomyReader, config, sfc);
+                        FacetResult result = facets.GetTopChildren(10, "field");
+                        if (pair.searcher.IndexReader.NumDocs > 0)
+                        {
+                            //System.out.println(pair.taxonomyReader.getSize());
+                            Assert.True(result.childCount > 0);
+                            Assert.True(result.labelValues.Length > 0);
+                        }
+
+                        //if (VERBOSE) {
+                        //System.out.println("TEST: facets=" + FacetTestUtils.toString(results.get(0)));
+                        //}
+                    }
+                    finally
+                    {
+                        mgr.Release(pair);
+                    }
+                }
+            }
+            finally
+            {
+                indexer.Join();
+                reopener.Join();
+            }
+
+            if (VERBOSE)
+            {
+                Console.WriteLine("TEST: now stop");
+            }
+
+            IOUtils.Close(mgr, tw, w, taxoDir, dir);
+        }
+
+        private class ThreadAnonymousInnerClassHelper : ThreadClass
+        {
+            private readonly TestSearcherTaxonomyManager outerInstance;
+
+            private AtomicBoolean stop;
+            private Lucene.Net.Facet.Taxonomy.SearcherTaxonomyManager mgr;
+
+            public ThreadAnonymousInnerClassHelper(TestSearcherTaxonomyManager outerInstance, AtomicBoolean stop, Lucene.Net.Facet.Taxonomy.SearcherTaxonomyManager mgr)
+            {
+                this.outerInstance = outerInstance;
+                this.stop = stop;
+                this.mgr = mgr;
+            }
+
+            public override void Run()
+            {
+                while (!stop.Get())
+                {
+                    try
+                    {
+                        // Sleep for up to 20 msec:
+                        Thread.Sleep(Random().Next(20));
+
+                        if (VERBOSE)
+                        {
+                            Console.WriteLine("TEST: reopen");
+                        }
+
+                        mgr.MaybeRefresh();
+
+                        if (VERBOSE)
+                        {
+                            Console.WriteLine("TEST: reopen done");
+                        }
+                    }
+                    catch (Exception ioe)
+                    {
+                        throw new Exception(ioe.Message, ioe);
+                    }
+                }
+            }
+        }
+
+        
+        [Test]
+        public virtual void TestDirectory()
+        {
+            Store.Directory indexDir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+            IndexWriter w = new IndexWriter(indexDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())));
+            var tw = new DirectoryTaxonomyWriter(taxoDir);
+            // first empty commit
+            w.Commit();
+            tw.Commit();
+            var mgr = new SearcherTaxonomyManager(indexDir, taxoDir, null);
+            FacetsConfig config = new FacetsConfig();
+            config.SetMultiValued("field", true);
+            AtomicBoolean stop = new AtomicBoolean();
+
+            // How many unique facets to index before stopping:
+            int ordLimit = TEST_NIGHTLY ? 100000 : 6000;
+
+            var indexer = new IndexerThread(w, config, tw, mgr, ordLimit, stop);
+            indexer.Start();
+
+            try
+            {
+                while (!stop.Get())
+                {
+                    SearcherAndTaxonomy pair = mgr.Acquire();
+                    try
+                    {
+                        //System.out.println("search maxOrd=" + pair.taxonomyReader.getSize());
+                        FacetsCollector sfc = new FacetsCollector();
+                        pair.searcher.Search(new MatchAllDocsQuery(), sfc);
+                        Facets facets = GetTaxonomyFacetCounts(pair.taxonomyReader, config, sfc);
+                        FacetResult result = facets.GetTopChildren(10, "field");
+                        if (pair.searcher.IndexReader.NumDocs > 0)
+                        {
+                            //System.out.println(pair.taxonomyReader.getSize());
+                            Assert.True(result.childCount > 0);
+                            Assert.True(result.labelValues.Length > 0);
+                        }
+
+                        //if (VERBOSE) {
+                        //System.out.println("TEST: facets=" + FacetTestUtils.toString(results.get(0)));
+                        //}
+                    }
+                    finally
+                    {
+                        mgr.Release(pair);
+                    }
+                }
+            }
+            finally
+            {
+                indexer.Join();
+            }
+
+            if (VERBOSE)
+            {
+                Console.WriteLine("TEST: now stop");
+            }
+
+            IOUtils.Close(mgr, tw, w, taxoDir, indexDir);
+        }
+
+        [Test]
+        public virtual void TestReplaceTaxonomyNrt()
+        {
+            Store.Directory dir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+            IndexWriter w = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())));
+            var tw = new DirectoryTaxonomyWriter(taxoDir);
+
+            Store.Directory taxoDir2 = NewDirectory();
+            var tw2 = new DirectoryTaxonomyWriter(taxoDir2);
+            tw2.Dispose();
+
+            var mgr = new SearcherTaxonomyManager(w, true, null, tw);
+            w.AddDocument(new Document());
+            tw.ReplaceTaxonomy(taxoDir2);
+            taxoDir2.Dispose();
+
+            try
+            {
+                mgr.MaybeRefresh();
+                Fail("should have hit exception");
+            }
+            catch (IllegalStateException)
+            {
+                // expected
+            }
+
+            IOUtils.Close(mgr, tw, w, taxoDir, dir);
+        }
+
+        [Test]
+        public virtual void TestReplaceTaxonomyDirectory()
+        {
+            Store.Directory indexDir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+            IndexWriter w = new IndexWriter(indexDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())));
+            var tw = new DirectoryTaxonomyWriter(taxoDir);
+            w.Commit();
+            tw.Commit();
+
+            Store.Directory taxoDir2 = NewDirectory();
+            var tw2 = new DirectoryTaxonomyWriter(taxoDir2);
+            tw2.AddCategory(new FacetLabel("a", "b"));
+            tw2.Dispose();
+
+            var mgr = new SearcherTaxonomyManager(indexDir, taxoDir, null);
+            SearcherAndTaxonomy pair = mgr.Acquire();
+            try
+            {
+                Assert.AreEqual(1, pair.taxonomyReader.Size);
+            }
+            finally
+            {
+                mgr.Release(pair);
+            }
+
+            w.AddDocument(new Document());
+            tw.ReplaceTaxonomy(taxoDir2);
+            taxoDir2.Dispose();
+            w.Commit();
+            tw.Commit();
+
+            mgr.MaybeRefresh();
+            pair = mgr.Acquire();
+            try
+            {
+                Assert.AreEqual(3, pair.taxonomyReader.Size);
+            }
+            finally
+            {
+                mgr.Release(pair);
+            }
+
+            IOUtils.Close(mgr, tw, w, taxoDir, indexDir);
+        }
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyCombined.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyCombined.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyCombined.cs
new file mode 100644
index 0000000..ee94742
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyCombined.cs
@@ -0,0 +1,1186 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading;
+using Lucene.Net.Support;
+using NUnit.Framework;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+
+    using DirectoryTaxonomyReader = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyReader;
+    using DirectoryTaxonomyWriter = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter;
+    using Directory = Lucene.Net.Store.Directory;
+    using LockObtainFailedException = Lucene.Net.Store.LockObtainFailedException;
+    using RAMDirectory = Lucene.Net.Store.RAMDirectory;
+    using SuppressCodecs = Lucene.Net.Util.LuceneTestCase.SuppressCodecs;
+
+    /*
+     * 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.
+     */
+
+
+    [TestFixture]
+    [SuppressCodecs]
+    public class TestTaxonomyCombined : FacetTestCase
+    {
+
+        /// <summary>
+        ///  The following categories will be added to the taxonomy by
+        ///  fillTaxonomy(), and tested by all tests below:
+        /// </summary>
+        private static readonly string[][] categories = new string[][]
+	  {
+		  new string[] {"Author", "Tom Clancy"},
+		  new string[] {"Author", "Richard Dawkins"},
+		  new string[] {"Author", "Richard Adams"},
+		  new string[] {"Price", "10", "11"},
+		  new string[] {"Price", "10", "12"},
+		  new string[] {"Price", "20", "27"},
+		  new string[] {"Date", "2006", "05"},
+		  new string[] {"Date", "2005"},
+		  new string[] {"Date", "2006"},
+		  new string[] {"Subject", "Nonfiction", "Children", "Animals"},
+		  new string[] {"Author", "Stephen Jay Gould"},
+		  new string[] {"Author", "\u05e0\u05d3\u05d1\u3042\u0628"}
+	  };
+
+        /// <summary>
+        ///  When adding the above categories with TaxonomyWriter.AddCategory(), 
+        ///  the following paths are expected to be returned:
+        ///  (note that currently the full path is not returned, and therefore
+        ///  not tested - rather, just the last component, the ordinal, is returned
+        ///  and tested.
+        /// </summary>
+        private static readonly int[][] ExpectedPaths =
+        {
+            new int[] {1, 2},
+            new int[] {1, 3},
+            new int[] {1, 4},
+            new int[] {5, 6, 7},
+            new int[] {5, 6, 8},
+            new int[] {5, 9, 10},
+            new int[] {11, 12, 13},
+            new int[] {11, 14},
+            new int[] {11, 12},
+            new int[] {15, 16, 17, 18},
+            new int[] {1, 19},
+            new int[] {1, 20}
+        };
+
+        /// <summary>
+        ///  The taxonomy index is expected to then contain the following
+        ///  generated categories, with increasing ordinals (note how parent
+        ///  categories are be added automatically when subcategories are added).
+        /// </summary>
+        private static readonly string[][] ExpectedCategories = new string[][] { new string[] { }, new string[] { "Author" }, new string[] { "Author", "Tom Clancy" }, new string[] { "Author", "Richard Dawkins" }, new string[] { "Author", "Richard Adams" }, new string[] { "Price" }, new string[] { "Price", "10" }, new string[] { "Price", "10", "11" }, new string[] { "Price", "10", "12" }, new string[] { "Price", "20" }, new string[] { "Price", "20", "27" }, new string[] { "Date" }, new string[] { "Date", "2006" }, new string[] { "Date", "2006", "05" }, new string[] { "Date", "2005" }, new string[] { "Subject" }, new string[] { "Subject", "Nonfiction" }, new string[] { "Subject", "Nonfiction", "Children" }, new string[] { "Subject", "Nonfiction", "Children", "Animals" }, new string[] { "Author", "Stephen Jay Gould" }, new string[] { "Author", "\u05e0\u05d3\u05d1\u3042\u0628" } };
+
+        /// <summary>
+        ///  fillTaxonomy adds the categories in the categories[] array, and asserts
+        ///  that the additions return exactly the ordinals (in the past - paths)
+        ///  specified in expectedPaths[].
+        ///  Note that this assumes that fillTaxonomy() is called on an empty taxonomy
+        ///  index. Calling it after something else was already added to the taxonomy
+        ///  index will surely have this method fail.
+        /// </summary>
+
+        public static void FillTaxonomy(TaxonomyWriter tw)
+        {
+            for (int i = 0; i < categories.Length; i++)
+            {
+                int ordinal = tw.AddCategory(new FacetLabel(categories[i]));
+                int expectedOrdinal = ExpectedPaths[i][ExpectedPaths[i].Length - 1];
+                if (ordinal != expectedOrdinal)
+                {
+                    Fail("For category " + Showcat(categories[i]) + " expected ordinal " + expectedOrdinal + ", but got " + ordinal);
+                }
+            }
+        }
+
+        public static string Showcat(string[] path)
+        {
+            if (path == null)
+            {
+                return "<null>";
+            }
+            if (path.Length == 0)
+            {
+                return "<empty>";
+            }
+            if (path.Length == 1 && path[0].Length == 0)
+            {
+                return "<\"\">";
+            }
+            StringBuilder sb = new StringBuilder(path[0]);
+            for (int i = 1; i < path.Length; i++)
+            {
+                sb.Append('/');
+                sb.Append(path[i]);
+            }
+            return sb.ToString();
+        }
+
+        private string Showcat(FacetLabel path)
+        {
+            if (path == null)
+            {
+                return "<null>";
+            }
+            if (path.length == 0)
+            {
+                return "<empty>";
+            }
+            return "<" + path.ToString() + ">";
+        }
+
+        /// <summary>
+        ///  Basic tests for TaxonomyWriter. Basically, we test that
+        ///  IndexWriter.AddCategory works, i.e. returns the expected ordinals
+        ///  (this is tested by calling the fillTaxonomy() method above).
+        ///  We do not test here that after writing the index can be read -
+        ///  this will be done in more tests below.
+        /// </summary>
+        [Test]
+        public virtual void TestWriter()
+        {
+            var indexDir = NewDirectory();
+            var tw = new DirectoryTaxonomyWriter(indexDir);
+            FillTaxonomy(tw);
+            // Also check TaxonomyWriter.getSize() - see that the taxonomy's size
+            // is what we expect it to be.
+            Assert.AreEqual(ExpectedCategories.Length, tw.Size);
+            tw.Dispose();
+            indexDir.Dispose();
+        }
+
+        /// <summary>
+        ///  testWriterTwice is exactly like testWriter, except that after adding
+        ///  all the categories, we add them again, and see that we get the same
+        ///  old ids again - not new categories.
+        /// </summary>
+        [Test]
+        public virtual void TestWriterTwice()
+        {
+            var indexDir = NewDirectory();
+            var tw = new DirectoryTaxonomyWriter(indexDir);
+            FillTaxonomy(tw);
+            // run fillTaxonomy again - this will try to add the same categories
+            // again, and check that we see the same ordinal paths again, not
+            // different ones. 
+            FillTaxonomy(tw);
+            // Let's check the number of categories again, to see that no
+            // extraneous categories were created:
+            Assert.AreEqual(ExpectedCategories.Length, tw.Size);
+            tw.Dispose();
+            indexDir.Dispose();
+        }
+
+        /// <summary>
+        ///  testWriterTwice2 is similar to testWriterTwice, except that the index
+        ///  is closed and reopened before attempting to write to it the same
+        ///  categories again. While testWriterTwice can get along with writing
+        ///  and reading correctly just to the cache, testWriterTwice2 checks also
+        ///  the actual disk read part of the writer:
+        /// </summary>
+        [Test]
+        public virtual void TestWriterTwice2()
+        {
+            var indexDir = NewDirectory();
+            var tw = new DirectoryTaxonomyWriter(indexDir);
+            FillTaxonomy(tw);
+            tw.Dispose();
+            tw = new DirectoryTaxonomyWriter(indexDir);
+            // run fillTaxonomy again - this will try to add the same categories
+            // again, and check that we see the same ordinals again, not different
+            // ones, and that the number of categories hasn't grown by the new
+            // additions
+            FillTaxonomy(tw);
+            Assert.AreEqual(ExpectedCategories.Length, tw.Size);
+            tw.Dispose();
+            indexDir.Dispose();
+        }
+
+        /// <summary>
+        /// testWriterTwice3 is yet another test which tests creating a taxonomy
+        /// in two separate writing sessions. This test used to fail because of
+        /// a bug involving commit(), explained below, and now should succeed.
+        /// </summary>
+        [Test]
+        public virtual void TestWriterTwice3()
+        {
+            var indexDir = NewDirectory();
+            // First, create and fill the taxonomy
+            var tw = new DirectoryTaxonomyWriter(indexDir);
+            FillTaxonomy(tw);
+            tw.Dispose();
+            // Now, open the same taxonomy and add the same categories again.
+            // After a few categories, the LuceneTaxonomyWriter implementation
+            // will stop looking for each category on disk, and rather read them
+            // all into memory and close it's reader. The bug was that it closed
+            // the reader, but forgot that it did (because it didn't set the reader
+            // reference to null).
+            tw = new DirectoryTaxonomyWriter(indexDir);
+            FillTaxonomy(tw);
+            // Add one new category, just to make commit() do something:
+            tw.AddCategory(new FacetLabel("hi"));
+            // Do a commit(). Here was a bug - if tw had a reader open, it should
+            // be reopened after the commit. However, in our case the reader should
+            // not be open (as explained above) but because it was not set to null,
+            // we forgot that, tried to reopen it, and got an AlreadyClosedException.
+            tw.Commit();
+            Assert.AreEqual(ExpectedCategories.Length + 1, tw.Size);
+            tw.Dispose();
+            indexDir.Dispose();
+        }
+
+        /// <summary>
+        ///  Another set of tests for the writer, which don't use an array and
+        ///  try to distill the different cases, and therefore may be more helpful
+        ///  for debugging a problem than testWriter() which is hard to know why
+        ///  or where it failed. 
+        /// </summary>
+        [Test]
+        public virtual void TestWriterSimpler()
+        {
+            var indexDir = NewDirectory();
+            var tw = new DirectoryTaxonomyWriter(indexDir);
+            Assert.AreEqual(1, tw.Size); // the root only
+            // Test that adding a new top-level category works
+            Assert.AreEqual(1, tw.AddCategory(new FacetLabel("a")));
+            Assert.AreEqual(2, tw.Size);
+            // Test that adding the same category again is noticed, and the
+            // same ordinal (and not a new one) is returned.
+            Assert.AreEqual(1, tw.AddCategory(new FacetLabel("a")));
+            Assert.AreEqual(2, tw.Size);
+            // Test that adding another top-level category returns a new ordinal,
+            // not the same one
+            Assert.AreEqual(2, tw.AddCategory(new FacetLabel("b")));
+            Assert.AreEqual(3, tw.Size);
+            // Test that adding a category inside one of the above adds just one
+            // new ordinal:
+            Assert.AreEqual(3, tw.AddCategory(new FacetLabel("a", "c")));
+            Assert.AreEqual(4, tw.Size);
+            // Test that adding the same second-level category doesn't do anything:
+            Assert.AreEqual(3, tw.AddCategory(new FacetLabel("a", "c")));
+            Assert.AreEqual(4, tw.Size);
+            // Test that adding a second-level category with two new components
+            // indeed adds two categories
+            Assert.AreEqual(5, tw.AddCategory(new FacetLabel("d", "e")));
+            Assert.AreEqual(6, tw.Size);
+            // Verify that the parents were added above in the order we expected
+            Assert.AreEqual(4, tw.AddCategory(new FacetLabel("d")));
+            // Similar, but inside a category that already exists:
+            Assert.AreEqual(7, tw.AddCategory(new FacetLabel("b", "d", "e")));
+            Assert.AreEqual(8, tw.Size);
+            // And now inside two levels of categories that already exist:
+            Assert.AreEqual(8, tw.AddCategory(new FacetLabel("b", "d", "f")));
+            Assert.AreEqual(9, tw.Size);
+
+            tw.Dispose();
+            indexDir.Dispose();
+        }
+
+        /// <summary>
+        ///  Test writing an empty index, and seeing that a reader finds in it
+        ///  the root category, and only it. We check all the methods on that
+        ///  root category return the expected results.
+        /// </summary>
+        [Test]
+        public virtual void TestRootOnly()
+        {
+            var indexDir = NewDirectory();
+            var tw = new DirectoryTaxonomyWriter(indexDir);
+            // right after opening the index, it should already contain the
+            // root, so have size 1:
+            Assert.AreEqual(1, tw.Size);
+            tw.Dispose();
+            var tr = new DirectoryTaxonomyReader(indexDir);
+            Assert.AreEqual(1, tr.Size);
+            Assert.AreEqual(0, tr.GetPath(0).length);
+            Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, tr.ParallelTaxonomyArrays.Parents()[0]);
+            Assert.AreEqual(0, tr.GetOrdinal(new FacetLabel()));
+            tr.Dispose(true);
+            indexDir.Dispose();
+        }
+
+        /// <summary>
+        ///  The following test is exactly the same as testRootOnly, except we
+        ///  do not close the writer before opening the reader. We want to see
+        ///  that the root is visible to the reader not only after the writer is
+        ///  closed, but immediately after it is created.
+        /// </summary>
+        [Test]
+        public virtual void TestRootOnly2()
+        {
+            var indexDir = NewDirectory();
+            var tw = new DirectoryTaxonomyWriter(indexDir);
+            tw.Commit();
+            var tr = new DirectoryTaxonomyReader(indexDir);
+            Assert.AreEqual(1, tr.Size);
+            Assert.AreEqual(0, tr.GetPath(0).length);
+            Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, tr.ParallelTaxonomyArrays.Parents()[0]);
+            Assert.AreEqual(0, tr.GetOrdinal(new FacetLabel()));
+            tw.Dispose();
+            tr.Dispose(true);
+            indexDir.Dispose();
+        }
+
+        /// <summary>
+        ///  Basic tests for TaxonomyReader's category <=> ordinal transformations
+        ///  (getSize(), getCategory() and getOrdinal()).
+        ///  We test that after writing the index, it can be read and all the
+        ///  categories and ordinals are there just as we expected them to be.
+        /// </summary>
+        [Test]
+        public virtual void TestReaderBasic()
+        {
+            var indexDir = NewDirectory();
+            var tw = new DirectoryTaxonomyWriter(indexDir);
+            FillTaxonomy(tw);
+            tw.Dispose();
+            var tr = new DirectoryTaxonomyReader(indexDir);
+
+            // test TaxonomyReader.getSize():
+            Assert.AreEqual(ExpectedCategories.Length, tr.Size);
+
+            // test round trips of ordinal => category => ordinal
+            for (int i = 0; i < tr.Size; i++)
+            {
+                Assert.AreEqual(i, tr.GetOrdinal(tr.GetPath(i)));
+            }
+
+            // test TaxonomyReader.getCategory():
+            for (int i = 1; i < tr.Size; i++)
+            {
+                FacetLabel expectedCategory = new FacetLabel(ExpectedCategories[i]);
+                FacetLabel category = tr.GetPath(i);
+                if (!expectedCategory.Equals(category))
+                {
+                    Fail("For ordinal " + i + " expected category " + Showcat(expectedCategory) + ", but got " + Showcat(category));
+                }
+            }
+            //  (also test invalid ordinals:)
+            Assert.Null(tr.GetPath(-1));
+            Assert.Null(tr.GetPath(tr.Size));
+            Assert.Null(tr.GetPath(TaxonomyReader.INVALID_ORDINAL));
+
+            // test TaxonomyReader.GetOrdinal():
+            for (int i = 1; i < ExpectedCategories.Length; i++)
+            {
+                int expectedOrdinal = i;
+                int ordinal = tr.GetOrdinal(new FacetLabel(ExpectedCategories[i]));
+                if (expectedOrdinal != ordinal)
+                {
+                    Fail("For category " + Showcat(ExpectedCategories[i]) + " expected ordinal " + expectedOrdinal + ", but got " + ordinal);
+                }
+            }
+            // (also test invalid categories:)
+            Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, tr.GetOrdinal(new FacetLabel("non-existant")));
+            Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, tr.GetOrdinal(new FacetLabel("Author", "Jules Verne")));
+
+            tr.Dispose();
+            indexDir.Dispose();
+        }
+
+        /// <summary>
+        ///  Tests for TaxonomyReader's getParent() method.
+        ///  We check it by comparing its results to those we could have gotten by
+        ///  looking at the category string paths (where the parentage is obvious).
+        ///  Note that after testReaderBasic(), we already know we can trust the
+        ///  ordinal <=> category conversions.
+        ///  
+        ///  Note: At the moment, the parent methods in the reader are deprecated,
+        ///  but this does not mean they should not be tested! Until they are
+        ///  removed (*if* they are removed), these tests should remain to see
+        ///  that they still work correctly.
+        /// </summary>
+
+        [Test]
+        public virtual void TestReaderParent()
+        {
+            var indexDir = NewDirectory();
+            var tw = new DirectoryTaxonomyWriter(indexDir);
+            FillTaxonomy(tw);
+            tw.Dispose();
+            var tr = new DirectoryTaxonomyReader(indexDir);
+
+            // check that the parent of the root ordinal is the invalid ordinal:
+            int[] parents = tr.ParallelTaxonomyArrays.Parents();
+            Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, parents[0]);
+
+            // check parent of non-root ordinals:
+            for (int ordinal = 1; ordinal < tr.Size; ordinal++)
+            {
+                FacetLabel me = tr.GetPath(ordinal);
+                int parentOrdinal = parents[ordinal];
+                FacetLabel parent = tr.GetPath(parentOrdinal);
+                if (parent == null)
+                {
+                    Fail("Parent of " + ordinal + " is " + parentOrdinal + ", but this is not a valid category.");
+                }
+                // verify that the parent is indeed my parent, according to the strings
+                if (!me.Subpath(me.length - 1).Equals(parent))
+                {
+                    Fail("Got parent " + parentOrdinal + " for ordinal " + ordinal + " but categories are " + Showcat(parent) + " and " + Showcat(me) + " respectively.");
+                }
+            }
+
+            tr.Dispose();
+            indexDir.Dispose();
+        }
+
+        /// <summary>
+        /// Tests for TaxonomyWriter's getParent() method. We check it by comparing
+        /// its results to those we could have gotten by looking at the category
+        /// string paths using a TaxonomyReader (where the parentage is obvious).
+        /// Note that after testReaderBasic(), we already know we can trust the
+        /// ordinal <=> category conversions from TaxonomyReader.
+        /// 
+        /// The difference between testWriterParent1 and testWriterParent2 is that
+        /// the former closes the taxonomy writer before reopening it, while the
+        /// latter does not.
+        /// 
+        /// This test code is virtually identical to that of testReaderParent().
+        /// </summary>
+        [Test]
+        public virtual void TestWriterParent1()
+        {
+            var indexDir = NewDirectory();
+            var tw = new DirectoryTaxonomyWriter(indexDir);
+            FillTaxonomy(tw);
+            tw.Dispose();
+            tw = new DirectoryTaxonomyWriter(indexDir);
+            var tr = new DirectoryTaxonomyReader(indexDir);
+
+            CheckWriterParent(tr, tw);
+
+            tw.Dispose();
+            tr.Dispose();
+            indexDir.Dispose();
+        }
+
+        [Test]
+        public virtual void TestWriterParent2()
+        {
+            var indexDir = NewDirectory();
+            var tw = new DirectoryTaxonomyWriter(indexDir);
+            FillTaxonomy(tw);
+            tw.Commit();
+            var tr = new DirectoryTaxonomyReader(indexDir);
+
+            CheckWriterParent(tr, tw);
+
+            tw.Dispose();
+            tr.Dispose();
+            indexDir.Dispose();
+        }
+
+        private void CheckWriterParent(TaxonomyReader tr, TaxonomyWriter tw)
+        {
+            // check that the parent of the root ordinal is the invalid ordinal:
+            Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, tw.GetParent(0));
+
+            // check parent of non-root ordinals:
+            for (int ordinal = 1; ordinal < tr.Size; ordinal++)
+            {
+                FacetLabel me = tr.GetPath(ordinal);
+                int parentOrdinal = tw.GetParent(ordinal);
+                FacetLabel parent = tr.GetPath(parentOrdinal);
+                if (parent == null)
+                {
+                    Fail("Parent of " + ordinal + " is " + parentOrdinal + ", but this is not a valid category.");
+                }
+                // verify that the parent is indeed my parent, according to the
+                // strings
+                if (!me.Subpath(me.length - 1).Equals(parent))
+                {
+                    Fail("Got parent " + parentOrdinal + " for ordinal " + ordinal + " but categories are " + Showcat(parent) + " and " + Showcat(me) + " respectively.");
+                }
+            }
+
+            // check parent of of invalid ordinals:
+            try
+            {
+                tw.GetParent(-1);
+                Fail("getParent for -1 should throw exception");
+            }
+            catch (System.IndexOutOfRangeException)
+            {
+                // ok
+            }
+            try
+            {
+                tw.GetParent(TaxonomyReader.INVALID_ORDINAL);
+                Fail("getParent for INVALID_ORDINAL should throw exception");
+            }
+            catch (System.IndexOutOfRangeException)
+            {
+                // ok
+            }
+            try
+            {
+                int parent = tw.GetParent(tr.Size);
+                Fail("getParent for getSize() should throw exception, but returned " + parent);
+            }
+            catch (System.IndexOutOfRangeException)
+            {
+                // ok
+            }
+        }
+
+        /// <summary>
+        /// Test TaxonomyReader's child browsing method, getChildrenArrays()
+        /// This only tests for correctness of the data on one example - we have
+        /// below further tests on data refresh etc.
+        /// </summary>
+        [Test]
+        public virtual void TestChildrenArrays()
+        {
+            var indexDir = NewDirectory();
+            var tw = new DirectoryTaxonomyWriter(indexDir);
+            FillTaxonomy(tw);
+            tw.Dispose();
+            var tr = new DirectoryTaxonomyReader(indexDir);
+            ParallelTaxonomyArrays ca = tr.ParallelTaxonomyArrays;
+            int[] youngestChildArray = ca.Children();
+            Assert.AreEqual(tr.Size, youngestChildArray.Length);
+            int[] olderSiblingArray = ca.Siblings();
+            Assert.AreEqual(tr.Size, olderSiblingArray.Length);
+            for (int i = 0; i < ExpectedCategories.Length; i++)
+            {
+                // find expected children by looking at all expectedCategories
+                // for children
+                List<int?> expectedChildren = new List<int?>();
+                for (int j = ExpectedCategories.Length - 1; j >= 0; j--)
+                {
+                    if (ExpectedCategories[j].Length != ExpectedCategories[i].Length + 1)
+                    {
+                        continue; // not longer by 1, so can't be a child
+                    }
+                    bool ischild = true;
+                    for (int k = 0; k < ExpectedCategories[i].Length; k++)
+                    {
+                        if (!ExpectedCategories[j][k].Equals(ExpectedCategories[i][k]))
+                        {
+                            ischild = false;
+                            break;
+                        }
+                    }
+                    if (ischild)
+                    {
+                        expectedChildren.Add(j);
+                    }
+                }
+                // check that children and expectedChildren are the same, with the
+                // correct reverse (youngest to oldest) order:
+                if (expectedChildren.Count == 0)
+                {
+                    Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, youngestChildArray[i]);
+                }
+                else
+                {
+                    int child = youngestChildArray[i];
+                    Assert.AreEqual((int)expectedChildren[0], child);
+                    for (int j = 1; j < expectedChildren.Count; j++)
+                    {
+                        child = olderSiblingArray[child];
+                        Assert.AreEqual((int)expectedChildren[j], child);
+                        // if child is INVALID_ORDINAL we should stop, but
+                        // AssertEquals would fail in this case anyway.
+                    }
+                    // When we're done comparing, olderSiblingArray should now point
+                    // to INVALID_ORDINAL, saying there are no more children. If it
+                    // doesn't, we found too many children...
+                    Assert.AreEqual(-1, olderSiblingArray[child]);
+                }
+            }
+            tr.Dispose();
+            indexDir.Dispose();
+        }
+
+        /// <summary>
+        /// Similar to testChildrenArrays, except rather than look at
+        /// expected results, we test for several "invariants" that the results
+        /// should uphold, e.g., that a child of a category indeed has this category
+        /// as its parent. This sort of test can more easily be extended to larger
+        /// example taxonomies, because we do not need to build the expected list
+        /// of categories like we did in the above test.
+        /// </summary>
+        [Test]
+        public virtual void TestChildrenArraysInvariants()
+        {
+            var indexDir = NewDirectory();
+            var tw = new DirectoryTaxonomyWriter(indexDir);
+            FillTaxonomy(tw);
+            tw.Dispose();
+            var tr = new DirectoryTaxonomyReader(indexDir);
+            ParallelTaxonomyArrays ca = tr.ParallelTaxonomyArrays;
+            int[] children = ca.Children();
+            Assert.AreEqual(tr.Size, children.Length);
+            int[] olderSiblingArray = ca.Siblings();
+            Assert.AreEqual(tr.Size, olderSiblingArray.Length);
+
+            // test that the "youngest child" of every category is indeed a child:
+            int[] parents = tr.ParallelTaxonomyArrays.Parents();
+            for (int i = 0; i < tr.Size; i++)
+            {
+                int youngestChild = children[i];
+                if (youngestChild != TaxonomyReader.INVALID_ORDINAL)
+                {
+                    Assert.AreEqual(i, parents[youngestChild]);
+                }
+            }
+
+            // test that the "older sibling" of every category is indeed older (lower)
+            // (it can also be INVALID_ORDINAL, which is lower than any ordinal)
+            for (int i = 0; i < tr.Size; i++)
+            {
+                Assert.True(olderSiblingArray[i] < i, "olderSiblingArray[" + i + "] should be <" + i);
+            }
+
+            // test that the "older sibling" of every category is indeed a sibling
+            // (they share the same parent)
+            for (int i = 0; i < tr.Size; i++)
+            {
+                int sibling = olderSiblingArray[i];
+                if (sibling == TaxonomyReader.INVALID_ORDINAL)
+                {
+                    continue;
+                }
+                Assert.AreEqual(parents[i], parents[sibling]);
+            }
+
+            // And now for slightly more complex (and less "invariant-like"...)
+            // tests:
+
+            // test that the "youngest child" is indeed the youngest (so we don't
+            // miss the first children in the chain)
+            for (int i = 0; i < tr.Size; i++)
+            {
+                // Find the really youngest child:
+                int j;
+                for (j = tr.Size - 1; j > i; j--)
+                {
+                    if (parents[j] == i)
+                    {
+                        break; // found youngest child
+                    }
+                }
+                if (j == i) // no child found
+                {
+                    j = TaxonomyReader.INVALID_ORDINAL;
+                }
+                Assert.AreEqual(j, children[i]);
+            }
+
+            // test that the "older sibling" is indeed the least oldest one - and
+            // not a too old one or -1 (so we didn't miss some children in the
+            // middle or the end of the chain).
+            for (int i = 0; i < tr.Size; i++)
+            {
+                // Find the youngest older sibling:
+                int j;
+                for (j = i - 1; j >= 0; j--)
+                {
+                    if (parents[j] == parents[i])
+                    {
+                        break; // found youngest older sibling
+                    }
+                }
+                if (j < 0) // no sibling found
+                {
+                    j = TaxonomyReader.INVALID_ORDINAL;
+                }
+                Assert.AreEqual(j, olderSiblingArray[i]);
+            }
+
+            tr.Dispose();
+            indexDir.Dispose();
+        }
+
+        /// <summary>
+        /// Test how getChildrenArrays() deals with the taxonomy's growth:
+        /// </summary>
+        [Test]
+        public virtual void TestChildrenArraysGrowth()
+        {
+            var indexDir = NewDirectory();
+            var tw = new DirectoryTaxonomyWriter(indexDir);
+            tw.AddCategory(new FacetLabel("hi", "there"));
+            tw.Commit();
+            var tr = new DirectoryTaxonomyReader(indexDir);
+            ParallelTaxonomyArrays ca = tr.ParallelTaxonomyArrays;
+            Assert.AreEqual(3, tr.Size);
+            Assert.AreEqual(3, ca.Siblings().Length);
+            Assert.AreEqual(3, ca.Children().Length);
+            Assert.True(Arrays.Equals(new int[] { 1, 2, -1 }, ca.Children()));
+            Assert.True(Arrays.Equals(new int[] { -1, -1, -1 }, ca.Siblings()));
+            tw.AddCategory(new FacetLabel("hi", "ho"));
+            tw.AddCategory(new FacetLabel("hello"));
+            tw.Commit();
+            // Before refresh, nothing changed..
+            ParallelTaxonomyArrays newca = tr.ParallelTaxonomyArrays;
+            Assert.AreSame(newca, ca); // we got exactly the same object
+            Assert.AreEqual(3, tr.Size);
+            Assert.AreEqual(3, ca.Siblings().Length);
+            Assert.AreEqual(3, ca.Children().Length);
+            // After the refresh, things change:
+            var newtr = TaxonomyReader.OpenIfChanged(tr);
+            Assert.NotNull(newtr);
+            tr.Dispose();
+            tr = newtr;
+            ca = tr.ParallelTaxonomyArrays;
+            Assert.AreEqual(5, tr.Size);
+            Assert.AreEqual(5, ca.Siblings().Length);
+            Assert.AreEqual(5, ca.Children().Length);
+            Assert.True(Arrays.Equals(new int[] { 4, 3, -1, -1, -1 }, ca.Children()));
+            Assert.True(Arrays.Equals(new int[] { -1, -1, -1, 2, 1 }, ca.Siblings()));
+            tw.Dispose();
+            tr.Dispose();
+            indexDir.Dispose();
+        }
+
+        // Test that getParentArrays is valid when retrieved during refresh
+        [Test]
+        public virtual void TestTaxonomyReaderRefreshRaces()
+        {
+            // compute base child arrays - after first chunk, and after the other
+            var indexDirBase = NewDirectory();
+            var twBase = new DirectoryTaxonomyWriter(indexDirBase);
+            twBase.AddCategory(new FacetLabel("a", "0"));
+            FacetLabel abPath = new FacetLabel("a", "b");
+            twBase.AddCategory(abPath);
+            twBase.Commit();
+            var trBase = new DirectoryTaxonomyReader(indexDirBase);
+
+            ParallelTaxonomyArrays ca1 = trBase.ParallelTaxonomyArrays;
+
+            int abOrd = trBase.GetOrdinal(abPath);
+            int abYoungChildBase1 = ca1.Children()[abOrd];
+
+            int numCategories = AtLeast(800);
+            for (int i = 0; i < numCategories; i++)
+            {
+                twBase.AddCategory(new FacetLabel("a", "b", Convert.ToString(i)));
+            }
+            twBase.Dispose();
+
+            var newTaxoReader = TaxonomyReader.OpenIfChanged(trBase);
+            Assert.NotNull(newTaxoReader);
+            trBase.Dispose();
+            trBase = newTaxoReader;
+
+            ParallelTaxonomyArrays ca2 = trBase.ParallelTaxonomyArrays;
+            int abYoungChildBase2 = ca2.Children()[abOrd];
+
+            int numRetries = AtLeast(50);
+            for (int retry = 0; retry < numRetries; retry++)
+            {
+                AssertConsistentYoungestChild(abPath, abOrd, abYoungChildBase1, abYoungChildBase2, retry, numCategories);
+            }
+
+            trBase.Dispose();
+            indexDirBase.Dispose();
+        }
+
+        
+        private void AssertConsistentYoungestChild(FacetLabel abPath, int abOrd, int abYoungChildBase1, int abYoungChildBase2, int retry, int numCategories)
+        {
+            var indexDir = new SlowRAMDirectory(-1, null); // no slowness for intialization
+            var tw = new DirectoryTaxonomyWriter(indexDir);
+            tw.AddCategory(new FacetLabel("a", "0"));
+            tw.AddCategory(abPath);
+            tw.Commit();
+
+            
+            var tr = new DirectoryTaxonomyReader(indexDir);
+            for (int i = 0; i < numCategories; i++)
+            {
+                var cp = new FacetLabel("a", "b", Convert.ToString(i));
+                tw.AddCategory(cp);
+                Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, tr.GetOrdinal(cp), "Ordinal of " + cp + " must be invalid until Taxonomy Reader was refreshed");
+            }
+            tw.Dispose();
+
+            
+            var stop = new AtomicBoolean(false);
+            Exception[] error = new Exception[] { null };
+            int[] retrieval = new int[] { 0 };
+
+            var thread = new ThreadAnonymousInnerClassHelper(this, abPath, abOrd, abYoungChildBase1, abYoungChildBase2, retry, tr, stop, error, retrieval);
+            thread.Start();
+
+            indexDir.SleepMillis = 1; // some delay for refresh
+            var newTaxoReader = TaxonomyReader.OpenIfChanged(tr);
+            if (newTaxoReader != null)
+            {
+                newTaxoReader.Dispose();
+            }
+
+            stop.Set(true);
+            thread.Join();
+            Assert.Null(error[0], "Unexpcted exception at retry " + retry + " retrieval " + retrieval[0] + ": \n" + stackTraceStr(error[0]));
+
+            tr.Dispose();
+        }
+
+        private class ThreadAnonymousInnerClassHelper : ThreadClass
+        {
+            private readonly TestTaxonomyCombined outerInstance;
+
+            private Lucene.Net.Facet.Taxonomy.FacetLabel abPath;
+            private int abOrd;
+            private int abYoungChildBase1;
+            private int abYoungChildBase2;
+            private int retry;
+            private DirectoryTaxonomyReader tr;
+            private AtomicBoolean stop;
+            private Exception[] error;
+            private int[] retrieval;
+
+            public ThreadAnonymousInnerClassHelper(TestTaxonomyCombined outerInstance, Lucene.Net.Facet.Taxonomy.FacetLabel abPath, int abOrd, int abYoungChildBase1, int abYoungChildBase2, int retry, DirectoryTaxonomyReader tr, AtomicBoolean stop, Exception[] error, int[] retrieval)
+                : base("Child Arrays Verifier")
+            {
+                this.outerInstance = outerInstance;
+                this.abPath = abPath;
+                this.abOrd = abOrd;
+                this.abYoungChildBase1 = abYoungChildBase1;
+                this.abYoungChildBase2 = abYoungChildBase2;
+                this.retry = retry;
+                this.tr = tr;
+                this.stop = stop;
+                this.error = error;
+                this.retrieval = retrieval;
+            }
+
+            public override void Run()
+            {
+                Priority = 1 + Priority;
+                try
+                {
+                    while (!stop.Get())
+                    {
+                        int lastOrd = tr.ParallelTaxonomyArrays.Parents().Length - 1;
+                        Assert.NotNull(tr.GetPath(lastOrd), "path of last-ord " + lastOrd + " is not found!");
+                        AssertChildrenArrays(tr.ParallelTaxonomyArrays, retry, retrieval[0]++);
+                        Thread.Sleep(10);// don't starve refresh()'s CPU, which sleeps every 50 bytes for 1 ms
+                    }
+                }
+                catch (Exception e)
+                {
+                    error[0] = e;
+                    stop.Set(true);
+                }
+            }
+
+            private void AssertChildrenArrays(ParallelTaxonomyArrays ca, int retry, int retrieval)
+            {
+                int abYoungChild = ca.Children()[abOrd];
+                Assert.True(abYoungChildBase1 == abYoungChild || abYoungChildBase2 == ca.Children()[abOrd], "Retry " + retry + ": retrieval: " + retrieval + ": wrong youngest child for category " + abPath + " (ord=" + abOrd + ") - must be either " + abYoungChildBase1 + " or " + abYoungChildBase2 + " but was: " + abYoungChild);
+            }
+        }
+
+        /// <summary>
+        /// Grab the stack trace into a string since the exception was thrown in a thread and we want the assert 
+        /// outside the thread to show the stack trace in case of failure.   
+        /// </summary>
+        private string stackTraceStr(Exception error)
+        {
+            if (error == null)
+            {
+                return "";
+            }
+
+            error.printStackTrace();
+            return error.StackTrace;
+        }
+
+        /// <summary>
+        ///  Test that if separate reader and writer objects are opened, new
+        ///  categories written into the writer are available to a reader only
+        ///  after a commit().
+        ///  Note that this test obviously doesn't cover all the different
+        ///  concurrency scenarios, all different methods, and so on. We may
+        ///  want to write more tests of this sort.
+        /// 
+        ///  This test simulates what would happen when there are two separate
+        ///  processes, one doing indexing, and the other searching, and each opens
+        ///  its own object (with obviously no connection between the objects) using
+        ///  the same disk files. Note, though, that this test does not test what
+        ///  happens when the two processes do their actual work at exactly the same
+        ///  time.
+        ///  It also doesn't test multi-threading.
+        /// </summary>
+        [Test]
+        public virtual void TestSeparateReaderAndWriter()
+        {
+            var indexDir = NewDirectory();
+            var tw = new DirectoryTaxonomyWriter(indexDir);
+            tw.Commit();
+            var tr = new DirectoryTaxonomyReader(indexDir);
+
+            Assert.AreEqual(1, tr.Size); // the empty taxonomy has size 1 (the root)
+            tw.AddCategory(new FacetLabel("Author"));
+            Assert.AreEqual(1, tr.Size); // still root only...
+            Assert.Null(TaxonomyReader.OpenIfChanged(tr)); // this is not enough, because tw.Commit() hasn't been done yet
+            Assert.AreEqual(1, tr.Size); // still root only...
+            tw.Commit();
+            Assert.AreEqual(1, tr.Size); // still root only...
+            var newTaxoReader = TaxonomyReader.OpenIfChanged(tr);
+            Assert.NotNull(newTaxoReader);
+            tr.Dispose();
+            tr = newTaxoReader;
+
+            int author = 1;
+            try
+            {
+                Assert.AreEqual(TaxonomyReader.ROOT_ORDINAL, tr.ParallelTaxonomyArrays.Parents()[author]);
+                // ok
+            }
+            catch (System.IndexOutOfRangeException)
+            {
+                Fail("After category addition, commit() and refresh(), getParent for " + author + " should NOT throw exception");
+            }
+            Assert.AreEqual(2, tr.Size); // finally, see there are two categories
+
+            // now, add another category, and verify that after commit and refresh
+            // the parent of this category is correct (this requires the reader
+            // to correctly update its prefetched parent vector), and that the
+            // old information also wasn't ruined:
+            tw.AddCategory(new FacetLabel("Author", "Richard Dawkins"));
+            int dawkins = 2;
+            tw.Commit();
+            newTaxoReader = TaxonomyReader.OpenIfChanged(tr);
+            Assert.NotNull(newTaxoReader);
+            tr.Dispose();
+            tr = newTaxoReader;
+            int[] parents = tr.ParallelTaxonomyArrays.Parents();
+            Assert.AreEqual(author, parents[dawkins]);
+            Assert.AreEqual(TaxonomyReader.ROOT_ORDINAL, parents[author]);
+            Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, parents[TaxonomyReader.ROOT_ORDINAL]);
+            Assert.AreEqual(3, tr.Size);
+            tw.Dispose();
+            tr.Dispose();
+            indexDir.Dispose();
+        }
+
+        [Test]
+        public virtual void TestSeparateReaderAndWriter2()
+        {
+            var indexDir = NewDirectory();
+            var tw = new DirectoryTaxonomyWriter(indexDir);
+            tw.Commit();
+            var tr = new DirectoryTaxonomyReader(indexDir);
+
+            // Test getOrdinal():
+            FacetLabel author = new FacetLabel("Author");
+
+            Assert.AreEqual(1, tr.Size); // the empty taxonomy has size 1 (the root)
+            Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, tr.GetOrdinal(author));
+            tw.AddCategory(author);
+            // before commit and refresh, no change:
+            Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, tr.GetOrdinal(author));
+            Assert.AreEqual(1, tr.Size); // still root only...
+            Assert.Null(TaxonomyReader.OpenIfChanged(tr)); // this is not enough, because tw.Commit() hasn't been done yet
+            Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, tr.GetOrdinal(author));
+            Assert.AreEqual(1, tr.Size); // still root only...
+            tw.Commit();
+            // still not enough before refresh:
+            Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, tr.GetOrdinal(author));
+            Assert.AreEqual(1, tr.Size); // still root only...
+            var newTaxoReader = TaxonomyReader.OpenIfChanged(tr);
+            Assert.NotNull(newTaxoReader);
+            tr.Dispose();
+            tr = newTaxoReader;
+            Assert.AreEqual(1, tr.GetOrdinal(author));
+            Assert.AreEqual(2, tr.Size);
+            tw.Dispose();
+            tr.Dispose();
+            indexDir.Dispose();
+        }
+
+        /// <summary>
+        /// Test what happens if we try to write to a locked taxonomy writer,
+        /// and see that we can unlock it and continue.
+        /// </summary>
+        [Test]
+        public virtual void TestWriterLock()
+        {
+            // native fslock impl gets angry if we use it, so use RAMDirectory explicitly.
+            var indexDir = new RAMDirectory();
+            var tw = new DirectoryTaxonomyWriter(indexDir);
+            tw.AddCategory(new FacetLabel("hi", "there"));
+            tw.Commit();
+            // we deliberately not close the write now, and keep it open and
+            // locked.
+            // Verify that the writer worked:
+            var tr = new DirectoryTaxonomyReader(indexDir);
+            Assert.AreEqual(2, tr.GetOrdinal(new FacetLabel("hi", "there")));
+            // Try to open a second writer, with the first one locking the directory.
+            // We expect to get a LockObtainFailedException.
+            try
+            {
+                Assert.Null(new DirectoryTaxonomyWriter(indexDir));
+                Fail("should have failed to write in locked directory");
+            }
+            catch (LockObtainFailedException)
+            {
+                // this is what we expect to happen.
+            }
+            // Remove the lock, and now the open should succeed, and we can
+            // write to the new writer.
+            DirectoryTaxonomyWriter.Unlock(indexDir);
+            var tw2 = new DirectoryTaxonomyWriter(indexDir);
+            tw2.AddCategory(new FacetLabel("hey"));
+            tw2.Dispose();
+            // See that the writer indeed wrote:
+            var newtr = TaxonomyReader.OpenIfChanged(tr);
+            Assert.NotNull(newtr);
+            tr.Dispose();
+            tr = newtr;
+            Assert.AreEqual(3, tr.GetOrdinal(new FacetLabel("hey")));
+            tr.Dispose();
+            tw.Dispose();
+            indexDir.Dispose();
+        }
+
+        /// <summary>
+        /// fillTaxonomyCheckPaths adds the categories in the categories[] array,
+        /// and asserts that the additions return exactly paths specified in
+        /// expectedPaths[]. This is the same add fillTaxonomy() but also checks
+        /// the correctness of getParent(), not just addCategory().
+        /// Note that this assumes that fillTaxonomyCheckPaths() is called on an empty
+        /// taxonomy index. Calling it after something else was already added to the
+        /// taxonomy index will surely have this method fail.
+        /// </summary>
+        [Test]
+        public static void FillTaxonomyCheckPaths(TaxonomyWriter tw)
+        {
+            for (int i = 0; i < categories.Length; i++)
+            {
+                int ordinal = tw.AddCategory(new FacetLabel(categories[i]));
+                int expectedOrdinal = ExpectedPaths[i][ExpectedPaths[i].Length - 1];
+                if (ordinal != expectedOrdinal)
+                {
+                    Fail("For category " + Showcat(categories[i]) + " expected ordinal " + expectedOrdinal + ", but got " + ordinal);
+                }
+                for (int j = ExpectedPaths[i].Length - 2; j >= 0; j--)
+                {
+                    ordinal = tw.GetParent(ordinal);
+                    expectedOrdinal = ExpectedPaths[i][j];
+                    if (ordinal != expectedOrdinal)
+                    {
+                        Fail("For category " + Showcat(categories[i]) + " expected ancestor level " + (ExpectedPaths[i].Length - 1 - j) + " was " + expectedOrdinal + ", but got " + ordinal);
+                    }
+                }
+            }
+        }
+
+        // After fillTaxonomy returned successfully, checkPaths() checks that
+        // the getParent() calls return as expected, from the table
+        public static void CheckPaths(TaxonomyWriter tw)
+        {
+            for (int i = 0; i < categories.Length; i++)
+            {
+                int ordinal = ExpectedPaths[i][ExpectedPaths[i].Length - 1];
+                for (int j = ExpectedPaths[i].Length - 2; j >= 0; j--)
+                {
+                    ordinal = tw.GetParent(ordinal);
+                    int expectedOrdinal = ExpectedPaths[i][j];
+                    if (ordinal != expectedOrdinal)
+                    {
+                        Fail("For category " + Showcat(categories[i]) + " expected ancestor level " + (ExpectedPaths[i].Length - 1 - j) + " was " + expectedOrdinal + ", but got " + ordinal);
+                    }
+                }
+                Assert.AreEqual(TaxonomyReader.ROOT_ORDINAL, tw.GetParent(ExpectedPaths[i][0]));
+            }
+            Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, tw.GetParent(TaxonomyReader.ROOT_ORDINAL));
+        }
+
+        /// <summary>
+        /// Basic test for TaxonomyWriter.getParent(). This is similar to testWriter
+        /// above, except we also check the parents of the added categories, not just
+        /// the categories themselves.
+        /// </summary>
+        [Test]
+        public virtual void TestWriterCheckPaths()
+        {
+            var indexDir = NewDirectory();
+            var tw = new DirectoryTaxonomyWriter(indexDir);
+            FillTaxonomyCheckPaths(tw);
+            // Also check TaxonomyWriter.getSize() - see that the taxonomy's size
+            // is what we expect it to be.
+            Assert.AreEqual(ExpectedCategories.Length, tw.Size);
+            tw.Dispose();
+            indexDir.Dispose();
+        }
+
+        /// <summary>
+        /// testWriterCheckPaths2 is the path-checking variant of testWriterTwice
+        /// and testWriterTwice2. After adding all the categories, we add them again,
+        /// and see that we get the same old ids and paths. We repeat the path checking
+        /// yet again after closing and opening the index for writing again - to see
+        /// that the reading of existing data from disk works as well.
+        /// </summary>
+        [Test]
+        public virtual void TestWriterCheckPaths2()
+        {
+            var indexDir = NewDirectory();
+            var tw = new DirectoryTaxonomyWriter(indexDir);
+            FillTaxonomy(tw);
+            CheckPaths(tw);
+            FillTaxonomy(tw);
+            CheckPaths(tw);
+            tw.Dispose();
+
+            tw = new DirectoryTaxonomyWriter(indexDir);
+            CheckPaths(tw);
+            FillTaxonomy(tw);
+            CheckPaths(tw);
+            tw.Dispose();
+            indexDir.Dispose();
+        }
+
+        [Test]
+        public virtual void TestNrt()
+        {
+            var dir = NewDirectory();
+            var writer = new DirectoryTaxonomyWriter(dir);
+            var reader = new DirectoryTaxonomyReader(writer);
+
+            FacetLabel cp = new FacetLabel("a");
+            writer.AddCategory(cp);
+            var newReader = TaxonomyReader.OpenIfChanged(reader);
+            Assert.NotNull(newReader, "expected a new instance");
+            Assert.AreEqual(2, newReader.Size);
+            Assert.AreNotSame(TaxonomyReader.INVALID_ORDINAL, newReader.GetOrdinal(cp));
+            reader.Dispose();
+            reader = newReader;
+
+            writer.Dispose();
+            reader.Dispose();
+
+            dir.Dispose();
+        }
+
+        //  TODO (Facet): test multiple readers, one writer. Have the multiple readers
+        //  using the same object (simulating threads) or different objects
+        //  (simulating processes).
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/982eaf60/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetAssociations.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetAssociations.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetAssociations.cs
new file mode 100644
index 0000000..06057ce
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetAssociations.cs
@@ -0,0 +1,278 @@
+using NUnit.Framework;
+
+namespace Lucene.Net.Facet.Taxonomy
+{
+
+    /*
+     * 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 Document = Lucene.Net.Documents.Document;
+    using DirectoryTaxonomyReader = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyReader;
+    using DirectoryTaxonomyWriter = Lucene.Net.Facet.Taxonomy.Directory.DirectoryTaxonomyWriter;
+    using IndexReader = Lucene.Net.Index.IndexReader;
+    using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter;
+    using IndexSearcher = Lucene.Net.Search.IndexSearcher;
+    using MatchAllDocsQuery = Lucene.Net.Search.MatchAllDocsQuery;
+    using Directory = Lucene.Net.Store.Directory;
+    using IOUtils = Lucene.Net.Util.IOUtils;
+
+    /// <summary>
+    /// Test for associations 
+    /// </summary>
+    [TestFixture]
+    public class TestTaxonomyFacetAssociations : FacetTestCase
+    {
+
+        private static Store.Directory dir;
+        private static IndexReader reader;
+        private static Store.Directory taxoDir;
+        private static TaxonomyReader taxoReader;
+
+        private static FacetsConfig config;
+
+
+        [TestFixtureSetUp]
+        public static void BeforeClass()
+        {
+            dir = NewDirectory();
+            taxoDir = NewDirectory();
+            // preparations - index, taxonomy, content
+
+            var taxoWriter = new DirectoryTaxonomyWriter(taxoDir);
+
+            // Cannot mix ints & floats in the same indexed field:
+            config = new FacetsConfig();
+            config.SetIndexFieldName("int", "$facets.int");
+            config.SetMultiValued("int", true);
+            config.SetIndexFieldName("float", "$facets.float");
+            config.SetMultiValued("float", true);
+
+            var writer = new RandomIndexWriter(Random(), dir);
+
+            // index documents, 50% have only 'b' and all have 'a'
+            for (int i = 0; i < 110; i++)
+            {
+                Document doc = new Document();
+                // every 11th document is added empty, this used to cause the association
+                // aggregators to go into an infinite loop
+                if (i % 11 != 0)
+                {
+                    doc.Add(new IntAssociationFacetField(2, "int", "a"));
+                    doc.Add(new FloatAssociationFacetField(0.5f, "float", "a"));
+                    if (i % 2 == 0) // 50
+                    {
+                        doc.Add(new IntAssociationFacetField(3, "int", "b"));
+                        doc.Add(new FloatAssociationFacetField(0.2f, "float", "b"));
+                    }
+                }
+                writer.AddDocument(config.Build(taxoWriter, doc));
+            }
+
+            taxoWriter.Dispose();
+            reader = writer.Reader;
+            writer.Dispose();
+            taxoReader = new DirectoryTaxonomyReader(taxoDir);
+        }
+
+        [TestFixtureTearDown]
+        public static void AfterClass()
+        {
+            reader.Dispose();
+            reader = null;
+            dir.Dispose();
+            dir = null;
+            taxoReader.Dispose(true);
+            taxoReader = null;
+            taxoDir.Dispose();
+            taxoDir = null;
+        }
+
+        [Test]
+        public virtual void TestIntSumAssociation()
+        {
+
+            FacetsCollector fc = new FacetsCollector();
+
+            IndexSearcher searcher = NewSearcher(reader);
+            searcher.Search(new MatchAllDocsQuery(), fc);
+
+            Facets facets = new TaxonomyFacetSumIntAssociations("$facets.int", taxoReader, config, fc);
+            Assert.AreEqual("dim=int path=[] value=-1 childCount=2\n  a (200)\n  b (150)\n", facets.GetTopChildren(10, "int").ToString());
+            Assert.AreEqual(200, (int)facets.GetSpecificValue("int", "a"), "Wrong count for category 'a'!");
+            Assert.AreEqual(150, (int)facets.GetSpecificValue("int", "b"), "Wrong count for category 'b'!");
+        }
+
+        [Test]
+        public virtual void TestFloatSumAssociation()
+        {
+            FacetsCollector fc = new FacetsCollector();
+
+            IndexSearcher searcher = NewSearcher(reader);
+            searcher.Search(new MatchAllDocsQuery(), fc);
+
+            Facets facets = new TaxonomyFacetSumFloatAssociations("$facets.float", taxoReader, config, fc);
+            Assert.AreEqual("dim=float path=[] value=-1.0 childCount=2\n  a (50.0)\n  b (9.999995)\n", facets.GetTopChildren(10, "float").ToString());
+            Assert.AreEqual(50f, (float)facets.GetSpecificValue("float", "a"), 0.00001, "Wrong count for category 'a'!");
+            Assert.AreEqual(10f, (float)facets.GetSpecificValue("float", "b"), 0.00001, "Wrong count for category 'b'!");
+        }
+
+        /// <summary>
+        /// Make sure we can test both int and float assocs in one
+        ///  index, as long as we send each to a different field. 
+        /// </summary>
+        [Test]
+        public virtual void TestIntAndFloatAssocation()
+        {
+            FacetsCollector fc = new FacetsCollector();
+
+            IndexSearcher searcher = NewSearcher(reader);
+            searcher.Search(new MatchAllDocsQuery(), fc);
+
+            Facets facets = new TaxonomyFacetSumFloatAssociations("$facets.float", taxoReader, config, fc);
+            Assert.AreEqual(50f, (float)facets.GetSpecificValue("float", "a"), 0.00001, "Wrong count for category 'a'!");
+            Assert.AreEqual(10f, (float)facets.GetSpecificValue("float", "b"), 0.00001, "Wrong count for category 'b'!");
+
+            facets = new TaxonomyFacetSumIntAssociations("$facets.int", taxoReader, config, fc);
+            Assert.AreEqual(200, (int)facets.GetSpecificValue("int", "a"), "Wrong count for category 'a'!");
+            Assert.AreEqual(150, (int)facets.GetSpecificValue("int", "b"), "Wrong count for category 'b'!");
+        }
+
+        
+        [Test]
+        public virtual void TestWrongIndexFieldName()
+        {
+            FacetsCollector fc = new FacetsCollector();
+
+            IndexSearcher searcher = NewSearcher(reader);
+            searcher.Search(new MatchAllDocsQuery(), fc);
+            Facets facets = new TaxonomyFacetSumFloatAssociations(taxoReader, config, fc);
+            try
+            {
+                facets.GetSpecificValue("float");
+                Fail("should have hit exc");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+
+            try
+            {
+                facets.GetTopChildren(10, "float");
+                Fail("should have hit exc");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+        }
+
+        [Test]
+        public virtual void TestMixedTypesInSameIndexField()
+        {
+            Store.Directory dir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+
+            TaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir);
+            FacetsConfig config = new FacetsConfig();
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir);
+
+            Document doc = new Document();
+            doc.Add(new IntAssociationFacetField(14, "a", "x"));
+            doc.Add(new FloatAssociationFacetField(55.0f, "b", "y"));
+            try
+            {
+                writer.AddDocument(config.Build(taxoWriter, doc));
+                Fail("did not hit expected exception");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+            IOUtils.Close(writer, taxoWriter, dir, taxoDir);
+        }
+
+        [Test]
+        public virtual void TestNoHierarchy()
+        {
+            Store.Directory dir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+
+            TaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir);
+            FacetsConfig config = new FacetsConfig();
+            config.SetHierarchical("a", true);
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir);
+
+            Document doc = new Document();
+            doc.Add(new IntAssociationFacetField(14, "a", "x"));
+            try
+            {
+                writer.AddDocument(config.Build(taxoWriter, doc));
+                Fail("did not hit expected exception");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+            IOUtils.Close(writer, taxoWriter, dir, taxoDir);
+        }
+
+        [Test]
+        public virtual void TestRequireDimCount()
+        {
+            Store.Directory dir = NewDirectory();
+            Store.Directory taxoDir = NewDirectory();
+
+            TaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir);
+            FacetsConfig config = new FacetsConfig();
+            config.SetRequireDimCount("a", true);
+            RandomIndexWriter writer = new RandomIndexWriter(Random(), dir);
+
+            Document doc = new Document();
+            doc.Add(new IntAssociationFacetField(14, "a", "x"));
+            try
+            {
+                writer.AddDocument(config.Build(taxoWriter, doc));
+                Fail("did not hit expected exception");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+            IOUtils.Close(writer, taxoWriter, dir, taxoDir);
+        }
+
+        [Test]
+        public virtual void TestIntSumAssociationDrillDown()
+        {
+            FacetsCollector fc = new FacetsCollector();
+
+            IndexSearcher searcher = NewSearcher(reader);
+            DrillDownQuery q = new DrillDownQuery(config);
+            q.Add("int", "b");
+            searcher.Search(q, fc);
+
+            Facets facets = new TaxonomyFacetSumIntAssociations("$facets.int", taxoReader, config, fc);
+            Assert.AreEqual("dim=int path=[] value=-1 childCount=2\n  b (150)\n  a (100)\n", facets.GetTopChildren(10, "int").ToString());
+            Assert.AreEqual(100, (int)facets.GetSpecificValue("int", "a"), "Wrong count for category 'a'!");
+            Assert.AreEqual(150, (int)facets.GetSpecificValue("int", "b"), "Wrong count for category 'b'!");
+        }
+
+    }
+
+}
\ No newline at end of file


[21/26] lucenenet git commit: dot-netify (change to upper case functions and properties)

Posted by sy...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/src/Lucene.Net.Tests/core/Facet/Range/TestRangeFacetCounts.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Range/TestRangeFacetCounts.cs b/src/Lucene.Net.Tests/core/Facet/Range/TestRangeFacetCounts.cs
index b9e4ef2..aae9008 100644
--- a/src/Lucene.Net.Tests/core/Facet/Range/TestRangeFacetCounts.cs
+++ b/src/Lucene.Net.Tests/core/Facet/Range/TestRangeFacetCounts.cs
@@ -538,14 +538,14 @@ namespace Lucene.Net.Facet.Range
                 ValueSource vs = new LongFieldSource("field");
                 Facets facets = new LongRangeFacetCounts("field", vs, sfc, fastMatchFilter, ranges);
                 FacetResult result = facets.GetTopChildren(10, "field");
-                Assert.AreEqual(numRange, result.labelValues.Length);
+                Assert.AreEqual(numRange, result.LabelValues.Length);
                 for (int rangeID = 0; rangeID < numRange; rangeID++)
                 {
                     if (VERBOSE)
                     {
                         Console.WriteLine("  range " + rangeID + " expectedCount=" + expectedCounts[rangeID]);
                     }
-                    LabelAndValue subNode = result.labelValues[rangeID];
+                    LabelAndValue subNode = result.LabelValues[rangeID];
                     Assert.AreEqual("r" + rangeID, subNode.label);
                     Assert.AreEqual(expectedCounts[rangeID], (int)subNode.value);
 
@@ -626,11 +626,11 @@ namespace Lucene.Net.Facet.Range
                         DoubleRange prevRange = ranges[Random().Next(rangeID)];
                         if (Random().NextBoolean())
                         {
-                            min = prevRange.min;
+                            min = prevRange.Min;
                         }
                         else
                         {
-                            min = prevRange.max;
+                            min = prevRange.Max;
                         }
                     }
                     else
@@ -644,11 +644,11 @@ namespace Lucene.Net.Facet.Range
                         DoubleRange prevRange = ranges[Random().Next(rangeID)];
                         if (Random().NextBoolean())
                         {
-                            max = prevRange.min;
+                            max = prevRange.Min;
                         }
                         else
                         {
-                            max = prevRange.max;
+                            max = prevRange.Max;
                         }
                     }
                     else
@@ -743,14 +743,14 @@ namespace Lucene.Net.Facet.Range
                 ValueSource vs = new FloatFieldSource("field");
                 Facets facets = new DoubleRangeFacetCounts("field", vs, sfc, fastMatchFilter, ranges);
                 FacetResult result = facets.GetTopChildren(10, "field");
-                Assert.AreEqual(numRange, result.labelValues.Length);
+                Assert.AreEqual(numRange, result.LabelValues.Length);
                 for (int rangeID = 0; rangeID < numRange; rangeID++)
                 {
                     if (VERBOSE)
                     {
                         Console.WriteLine("TEST: verify range " + rangeID + " expectedCount=" + expectedCounts[rangeID]);
                     }
-                    LabelAndValue subNode = result.labelValues[rangeID];
+                    LabelAndValue subNode = result.LabelValues[rangeID];
                     Assert.AreEqual("r" + rangeID, subNode.label);
                     Assert.AreEqual(expectedCounts[rangeID], (int)subNode.value);
 
@@ -762,11 +762,11 @@ namespace Lucene.Net.Facet.Range
                     {
                         if (Random().NextBoolean())
                         {
-                            ddq.Add("field", NumericRangeFilter.NewFloatRange("field", (float)range.min, (float)range.max, range.minInclusive, range.maxInclusive));
+                            ddq.Add("field", NumericRangeFilter.NewFloatRange("field", (float)range.Min, (float)range.Max, range.MinInclusive, range.MaxInclusive));
                         }
                         else
                         {
-                            ddq.Add("field", NumericRangeQuery.NewFloatRange("field", (float)range.min, (float)range.max, range.minInclusive, range.maxInclusive));
+                            ddq.Add("field", NumericRangeQuery.NewFloatRange("field", (float)range.Min, (float)range.Max, range.MinInclusive, range.MaxInclusive));
                         }
                     }
                     else
@@ -827,11 +827,11 @@ namespace Lucene.Net.Facet.Range
                         DoubleRange prevRange = ranges[Random().Next(rangeID)];
                         if (Random().NextBoolean())
                         {
-                            min = prevRange.min;
+                            min = prevRange.Min;
                         }
                         else
                         {
-                            min = prevRange.max;
+                            min = prevRange.Max;
                         }
                     }
                     else
@@ -845,11 +845,11 @@ namespace Lucene.Net.Facet.Range
                         DoubleRange prevRange = ranges[Random().Next(rangeID)];
                         if (Random().NextBoolean())
                         {
-                            max = prevRange.min;
+                            max = prevRange.Min;
                         }
                         else
                         {
-                            max = prevRange.max;
+                            max = prevRange.Max;
                         }
                     }
                     else
@@ -929,14 +929,14 @@ namespace Lucene.Net.Facet.Range
                 ValueSource vs = new DoubleFieldSource("field");
                 Facets facets = new DoubleRangeFacetCounts("field", vs, sfc, fastMatchFilter, ranges);
                 FacetResult result = facets.GetTopChildren(10, "field");
-                Assert.AreEqual(numRange, result.labelValues.Length);
+                Assert.AreEqual(numRange, result.LabelValues.Length);
                 for (int rangeID = 0; rangeID < numRange; rangeID++)
                 {
                     if (VERBOSE)
                     {
                         Console.WriteLine("  range " + rangeID + " expectedCount=" + expectedCounts[rangeID]);
                     }
-                    LabelAndValue subNode = result.labelValues[rangeID];
+                    LabelAndValue subNode = result.LabelValues[rangeID];
                     Assert.AreEqual("r" + rangeID, subNode.label);
                     Assert.AreEqual(expectedCounts[rangeID], (int)subNode.value);
 
@@ -948,11 +948,11 @@ namespace Lucene.Net.Facet.Range
                     {
                         if (Random().NextBoolean())
                         {
-                            ddq.Add("field", NumericRangeFilter.NewDoubleRange("field", range.min, range.max, range.minInclusive, range.maxInclusive));
+                            ddq.Add("field", NumericRangeFilter.NewDoubleRange("field", range.Min, range.Max, range.MinInclusive, range.MaxInclusive));
                         }
                         else
                         {
-                            ddq.Add("field", NumericRangeQuery.NewDoubleRange("field", range.min, range.max, range.minInclusive, range.maxInclusive));
+                            ddq.Add("field", NumericRangeQuery.NewDoubleRange("field", range.Min, range.Max, range.MinInclusive, range.MaxInclusive));
                         }
                     }
                     else

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestAddTaxonomy.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestAddTaxonomy.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestAddTaxonomy.cs
index 2ece8ca..df4f491 100644
--- a/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestAddTaxonomy.cs
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestAddTaxonomy.cs
@@ -262,14 +262,14 @@ namespace Lucene.Net.Facet.Taxonomy.Directory
             // now add the taxonomy to an empty taxonomy, while adding the categories
             // again, in parallel -- in the end, no duplicate categories should exist.
             Directory dest = NewDirectory();
-            var destTW = new DirectoryTaxonomyWriter(dest);
-            ThreadClass t = new ThreadAnonymousInnerClassHelper2(this, numCategories, destTW);
+            var destTw = new DirectoryTaxonomyWriter(dest);
+            ThreadClass t = new ThreadAnonymousInnerClassHelper2(this, numCategories, destTw);
             t.Start();
 
             OrdinalMap map = new MemoryOrdinalMap();
-            destTW.AddTaxonomy(src, map);
+            destTw.AddTaxonomy(src, map);
             t.Join();
-            destTW.Dispose();
+            destTw.Dispose();
 
             // now validate
 
@@ -282,7 +282,7 @@ namespace Lucene.Net.Facet.Taxonomy.Directory
                 FacetLabel cat = dtr.GetPath(i);
                 Assert.True(categories.Add(cat), "category " + cat + " already existed");
             }
-            (dtr).Dispose();
+            dtr.Dispose();
 
             IOUtils.Close(src, dest);
         }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestConcurrentFacetedIndexing.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestConcurrentFacetedIndexing.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestConcurrentFacetedIndexing.cs
index 86dd3a9..5ee17a6 100644
--- a/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestConcurrentFacetedIndexing.cs
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestConcurrentFacetedIndexing.cs
@@ -153,7 +153,7 @@ namespace Lucene.Net.Facet.Taxonomy.Directory
             {
                 FacetLabel cp = new FacetLabel(FacetsConfig.StringToPath(cat));
                 Assert.True(tr.GetOrdinal(cp) > 0, "category not found " + cp);
-                int level = cp.length;
+                int level = cp.Length;
                 int parentOrd = 0; // for root, parent is always virtual ROOT (ord=0)
                 FacetLabel path = null;
                 for (int i = 0; i < level; i++)
@@ -205,10 +205,10 @@ namespace Lucene.Net.Facet.Taxonomy.Directory
 
                             FacetLabel label = new FacetLabel(ff.dim, ff.path);
                             // add all prefixes to values
-                            int level = label.length;
+                            int level = label.Length;
                             while (level > 0)
                             {
-                                string s = FacetsConfig.PathToString(label.components, level);
+                                string s = FacetsConfig.PathToString(label.Components, level);
                                 values[s] = s;
                                 --level;
                             }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestDirectoryTaxonomyReader.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestDirectoryTaxonomyReader.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestDirectoryTaxonomyReader.cs
index d933618..15f30df 100644
--- a/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestDirectoryTaxonomyReader.cs
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestDirectoryTaxonomyReader.cs
@@ -592,8 +592,8 @@ namespace Lucene.Net.Facet.Taxonomy.Directory
             while (roots.Count > 0)
             {
                 FacetLabel root = taxoReader.GetPath(it.Next());
-                Assert.AreEqual(1, root.length);
-                Assert.True(roots.Remove(root.components[0]));
+                Assert.AreEqual(1, root.Length);
+                Assert.True(roots.Remove(root.Components[0]));
             }
             Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, it.Next());
 
@@ -607,8 +607,8 @@ namespace Lucene.Net.Facet.Taxonomy.Directory
                 while ((child = it.Next()) != TaxonomyReader.INVALID_ORDINAL)
                 {
                     FacetLabel path = taxoReader.GetPath(child);
-                    Assert.AreEqual(2, path.length);
-                    Assert.AreEqual(path.components[0], i == 0 ? "a" : "b");
+                    Assert.AreEqual(2, path.Length);
+                    Assert.AreEqual(path.Components[0], i == 0 ? "a" : "b");
                     ++numChildren;
                 }
                 int expected = i == 0 ? numA : numB;

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestDirectoryTaxonomyWriter.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestDirectoryTaxonomyWriter.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestDirectoryTaxonomyWriter.cs
index ebf5455..f5ddd11 100644
--- a/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestDirectoryTaxonomyWriter.cs
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/Directory/TestDirectoryTaxonomyWriter.cs
@@ -336,7 +336,7 @@ namespace Lucene.Net.Facet.Taxonomy.Directory
             {
                 FacetLabel cp = new FacetLabel(FacetsConfig.StringToPath(cat));
                 Assert.True(dtr.GetOrdinal(cp) > 0, "category not found " + cp);
-                int level = cp.length;
+                int level = cp.Length;
                 int parentOrd = 0; // for root, parent is always virtual ROOT (ord=0)
                 FacetLabel path = new FacetLabel();
                 for (int i = 0; i < level; i++)
@@ -380,10 +380,10 @@ namespace Lucene.Net.Facet.Taxonomy.Directory
                         FacetLabel cp = new FacetLabel(Convert.ToString(value / 1000), Convert.ToString(value / 10000), Convert.ToString(value / 100000), Convert.ToString(value));
                         int ord = tw.AddCategory(cp);
                         Assert.True(tw.GetParent(ord) != -1, "invalid parent for ordinal " + ord + ", category " + cp);
-                        string l1 = FacetsConfig.PathToString(cp.components, 1);
-                        string l2 = FacetsConfig.PathToString(cp.components, 2);
-                        string l3 = FacetsConfig.PathToString(cp.components, 3);
-                        string l4 = FacetsConfig.PathToString(cp.components, 4);
+                        string l1 = FacetsConfig.PathToString(cp.Components, 1);
+                        string l2 = FacetsConfig.PathToString(cp.Components, 2);
+                        string l3 = FacetsConfig.PathToString(cp.Components, 3);
+                        string l4 = FacetsConfig.PathToString(cp.Components, 4);
                         values[l1] = l1;
                         values[l2] = l2;
                         values[l3] = l3;

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestFacetLabel.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestFacetLabel.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestFacetLabel.cs
index 7f09146..0226137 100644
--- a/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestFacetLabel.cs
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestFacetLabel.cs
@@ -32,9 +32,9 @@ namespace Lucene.Net.Facet.Taxonomy
         [Test]
         public virtual void TestBasic()
         {
-            Assert.AreEqual(0, (new FacetLabel()).length);
-            Assert.AreEqual(1, (new FacetLabel("hello")).length);
-            Assert.AreEqual(2, (new FacetLabel("hello", "world")).length);
+            Assert.AreEqual(0, (new FacetLabel()).Length);
+            Assert.AreEqual(1, (new FacetLabel("hello")).Length);
+            Assert.AreEqual(2, (new FacetLabel("hello", "world")).Length);
         }
 
         [Test]
@@ -59,7 +59,7 @@ namespace Lucene.Net.Facet.Taxonomy
             FacetLabel cp = new FacetLabel(components);
             for (int i = 0; i < components.Length; i++)
             {
-                Assert.AreEqual(i, Convert.ToInt32(cp.components[i]));
+                Assert.AreEqual(i, Convert.ToInt32(cp.Components[i]));
             }
         }
 
@@ -71,7 +71,7 @@ namespace Lucene.Net.Facet.Taxonomy
             // If we change this default later, we also need to change this
             // test.
             FacetLabel p = new FacetLabel();
-            Assert.AreEqual(0, p.length);
+            Assert.AreEqual(0, p.Length);
             Assert.AreEqual("FacetLabel: []", p.ToString());
         }
 
@@ -79,18 +79,18 @@ namespace Lucene.Net.Facet.Taxonomy
         public virtual void TestSubPath()
         {
             FacetLabel p = new FacetLabel("hi", "there", "man");
-            Assert.AreEqual(p.length, 3);
+            Assert.AreEqual(p.Length, 3);
 
             FacetLabel p1 = p.Subpath(2);
-            Assert.AreEqual(2, p1.length);
+            Assert.AreEqual(2, p1.Length);
             Assert.AreEqual("FacetLabel: [hi, there]", p1.ToString());
 
             p1 = p.Subpath(1);
-            Assert.AreEqual(1, p1.length);
+            Assert.AreEqual(1, p1.Length);
             Assert.AreEqual("FacetLabel: [hi]", p1.ToString());
 
             p1 = p.Subpath(0);
-            Assert.AreEqual(0, p1.length);
+            Assert.AreEqual(0, p1.Length);
             Assert.AreEqual("FacetLabel: []", p1.ToString());
 
             // with all the following lengths, the prefix should be the whole path 
@@ -98,7 +98,7 @@ namespace Lucene.Net.Facet.Taxonomy
             for (int i = 0; i < lengths.Length; i++)
             {
                 p1 = p.Subpath(lengths[i]);
-                Assert.AreEqual(3, p1.length);
+                Assert.AreEqual(3, p1.Length);
                 Assert.AreEqual("FacetLabel: [hi, there, man]", p1.ToString());
                 Assert.AreEqual(p, p1);
             }
@@ -133,7 +133,7 @@ namespace Lucene.Net.Facet.Taxonomy
         public virtual void TestArrayConstructor()
         {
             FacetLabel p = new FacetLabel("hello", "world", "yo");
-            Assert.AreEqual(3, p.length);
+            Assert.AreEqual(3, p.Length);
             Assert.AreEqual("FacetLabel: [hello, world, yo]", p.ToString());
         }
 

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestSearcherTaxonomyManager.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestSearcherTaxonomyManager.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestSearcherTaxonomyManager.cs
index 2d90fe4..dd93d18 100644
--- a/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestSearcherTaxonomyManager.cs
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestSearcherTaxonomyManager.cs
@@ -182,8 +182,8 @@ namespace Lucene.Net.Facet.Taxonomy
                         if (pair.searcher.IndexReader.NumDocs > 0)
                         {
                             //System.out.println(pair.taxonomyReader.getSize());
-                            Assert.True(result.childCount > 0);
-                            Assert.True(result.labelValues.Length > 0);
+                            Assert.True(result.ChildCount > 0);
+                            Assert.True(result.LabelValues.Length > 0);
                         }
 
                         //if (VERBOSE) {
@@ -290,8 +290,8 @@ namespace Lucene.Net.Facet.Taxonomy
                         if (pair.searcher.IndexReader.NumDocs > 0)
                         {
                             //System.out.println(pair.taxonomyReader.getSize());
-                            Assert.True(result.childCount > 0);
-                            Assert.True(result.labelValues.Length > 0);
+                            Assert.True(result.ChildCount > 0);
+                            Assert.True(result.LabelValues.Length > 0);
                         }
 
                         //if (VERBOSE) {

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyCombined.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyCombined.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyCombined.cs
index ee94742..caf1f09 100644
--- a/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyCombined.cs
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyCombined.cs
@@ -141,7 +141,7 @@ namespace Lucene.Net.Facet.Taxonomy
             {
                 return "<null>";
             }
-            if (path.length == 0)
+            if (path.Length == 0)
             {
                 return "<empty>";
             }
@@ -311,7 +311,7 @@ namespace Lucene.Net.Facet.Taxonomy
             tw.Dispose();
             var tr = new DirectoryTaxonomyReader(indexDir);
             Assert.AreEqual(1, tr.Size);
-            Assert.AreEqual(0, tr.GetPath(0).length);
+            Assert.AreEqual(0, tr.GetPath(0).Length);
             Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, tr.ParallelTaxonomyArrays.Parents()[0]);
             Assert.AreEqual(0, tr.GetOrdinal(new FacetLabel()));
             tr.Dispose(true);
@@ -332,7 +332,7 @@ namespace Lucene.Net.Facet.Taxonomy
             tw.Commit();
             var tr = new DirectoryTaxonomyReader(indexDir);
             Assert.AreEqual(1, tr.Size);
-            Assert.AreEqual(0, tr.GetPath(0).length);
+            Assert.AreEqual(0, tr.GetPath(0).Length);
             Assert.AreEqual(TaxonomyReader.INVALID_ORDINAL, tr.ParallelTaxonomyArrays.Parents()[0]);
             Assert.AreEqual(0, tr.GetOrdinal(new FacetLabel()));
             tw.Dispose();
@@ -434,7 +434,7 @@ namespace Lucene.Net.Facet.Taxonomy
                     Fail("Parent of " + ordinal + " is " + parentOrdinal + ", but this is not a valid category.");
                 }
                 // verify that the parent is indeed my parent, according to the strings
-                if (!me.Subpath(me.length - 1).Equals(parent))
+                if (!me.Subpath(me.Length - 1).Equals(parent))
                 {
                     Fail("Got parent " + parentOrdinal + " for ordinal " + ordinal + " but categories are " + Showcat(parent) + " and " + Showcat(me) + " respectively.");
                 }
@@ -507,7 +507,7 @@ namespace Lucene.Net.Facet.Taxonomy
                 }
                 // verify that the parent is indeed my parent, according to the
                 // strings
-                if (!me.Subpath(me.length - 1).Equals(parent))
+                if (!me.Subpath(me.Length - 1).Equals(parent))
                 {
                     Fail("Got parent " + parentOrdinal + " for ordinal " + ordinal + " but categories are " + Showcat(parent) + " and " + Showcat(me) + " respectively.");
                 }
@@ -1068,7 +1068,6 @@ namespace Lucene.Net.Facet.Taxonomy
         /// taxonomy index. Calling it after something else was already added to the
         /// taxonomy index will surely have this method fail.
         /// </summary>
-        [Test]
         public static void FillTaxonomyCheckPaths(TaxonomyWriter tw)
         {
             for (int i = 0; i < categories.Length; i++)

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetCounts.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetCounts.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetCounts.cs
index b4bb012..8038313 100644
--- a/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetCounts.cs
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetCounts.cs
@@ -353,8 +353,8 @@ namespace Lucene.Net.Facet.Taxonomy
             }
 
             FacetResult result = facets.GetTopChildren(10, "a");
-            Assert.AreEqual(1, result.labelValues.Length);
-            Assert.AreEqual(1, (int)result.labelValues[0].value);
+            Assert.AreEqual(1, result.LabelValues.Length);
+            Assert.AreEqual(1, (int)result.LabelValues[0].value);
 
             IOUtils.Close(writer, taxoWriter, searcher.IndexReader, taxoReader, dir, taxoDir);
         }
@@ -431,9 +431,9 @@ namespace Lucene.Net.Facet.Taxonomy
             searcher.Search(new MatchAllDocsQuery(), c);
 
             Facets facets = GetTaxonomyFacetCounts(taxoReader, config, c);
-            Assert.AreEqual(1, facets.GetTopChildren(10, "dim").value);
-            Assert.AreEqual(1, facets.GetTopChildren(10, "dim2").value);
-            Assert.AreEqual(1, facets.GetTopChildren(10, "dim3").value);
+            Assert.AreEqual(1, facets.GetTopChildren(10, "dim").Value);
+            Assert.AreEqual(1, facets.GetTopChildren(10, "dim2").Value);
+            Assert.AreEqual(1, facets.GetTopChildren(10, "dim3").Value);
             try
             {
                 Assert.AreEqual(1, facets.GetSpecificValue("dim"));
@@ -490,9 +490,9 @@ namespace Lucene.Net.Facet.Taxonomy
             Facets facets = GetTaxonomyFacetCounts(taxoReader, config, c);
 
             FacetResult result = facets.GetTopChildren(int.MaxValue, "dim");
-            Assert.AreEqual(numLabels, result.labelValues.Length);
+            Assert.AreEqual(numLabels, result.LabelValues.Length);
             var allLabels = new HashSet<string>();
-            foreach (LabelAndValue labelValue in result.labelValues)
+            foreach (LabelAndValue labelValue in result.LabelValues)
             {
                 allLabels.Add(labelValue.label);
                 Assert.AreEqual(1, (int)labelValue.value);
@@ -582,8 +582,8 @@ namespace Lucene.Net.Facet.Taxonomy
             NewSearcher(r).Search(new MatchAllDocsQuery(), sfc);
             Facets facets1 = GetTaxonomyFacetCounts(taxoReader, config, sfc);
             Facets facets2 = GetTaxonomyFacetCounts(taxoReader, config, sfc, "$b");
-            Assert.AreEqual(r.MaxDoc, (int)facets1.GetTopChildren(10, "a").value);
-            Assert.AreEqual(r.MaxDoc, (int)facets2.GetTopChildren(10, "b").value);
+            Assert.AreEqual(r.MaxDoc, (int)facets1.GetTopChildren(10, "a").Value);
+            Assert.AreEqual(r.MaxDoc, (int)facets2.GetTopChildren(10, "b").Value);
             IOUtils.Close(taxoWriter, iw, taxoReader, taxoDir, r, indexDir);
         }
 
@@ -613,7 +613,7 @@ namespace Lucene.Net.Facet.Taxonomy
             Facets facets = GetTaxonomyFacetCounts(taxoReader, config, sfc);
             foreach (FacetResult result in facets.GetAllDims(10))
             {
-                Assert.AreEqual(r.NumDocs, (int)result.value);
+                Assert.AreEqual(r.NumDocs, (int)result.Value);
             }
 
             IOUtils.Close(taxoWriter, iw, taxoReader, taxoDir, r, indexDir);
@@ -673,7 +673,7 @@ namespace Lucene.Net.Facet.Taxonomy
             NewSearcher(r).Search(new MatchAllDocsQuery(), sfc);
             Facets facets = GetTaxonomyFacetCounts(taxoReader, config, sfc);
 
-            Assert.AreEqual(10, facets.GetTopChildren(2, "a").childCount);
+            Assert.AreEqual(10, facets.GetTopChildren(2, "a").ChildCount);
 
             IOUtils.Close(taxoWriter, iw, taxoReader, taxoDir, r, indexDir);
         }
@@ -733,8 +733,8 @@ namespace Lucene.Net.Facet.Taxonomy
             indexSearcher.Search(q, sfc);
             Facets facets = GetTaxonomyFacetCounts(taxoReader, config, sfc);
             FacetResult result = facets.GetTopChildren(10, "A");
-            Assert.AreEqual(2, result.labelValues.Length, "wrong number of children");
-            foreach (LabelAndValue labelValue in result.labelValues)
+            Assert.AreEqual(2, result.LabelValues.Length, "wrong number of children");
+            foreach (LabelAndValue labelValue in result.LabelValues)
             {
                 Assert.AreEqual(2, (int)labelValue.value, "wrong weight for child " + labelValue.label);
             }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetCounts2.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetCounts2.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetCounts2.cs
index f461dde..865f993 100644
--- a/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetCounts2.cs
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetCounts2.cs
@@ -293,14 +293,14 @@ namespace Lucene.Net.Facet.Taxonomy
             searcher.Search(q, sfc);
             Facets facets = GetTaxonomyFacetCounts(taxoReader, Config, sfc);
             FacetResult result = facets.GetTopChildren(NUM_CHILDREN_CP_A, CP_A);
-            Assert.AreEqual(-1, (int)result.value);
-            foreach (LabelAndValue labelValue in result.labelValues)
+            Assert.AreEqual(-1, (int)result.Value);
+            foreach (LabelAndValue labelValue in result.LabelValues)
             {
                 Assert.AreEqual(termExpectedCounts[CP_A + "/" + labelValue.label], labelValue.value);
             }
             result = facets.GetTopChildren(NUM_CHILDREN_CP_B, CP_B);
-            Assert.AreEqual(termExpectedCounts[CP_B], result.value);
-            foreach (LabelAndValue labelValue in result.labelValues)
+            Assert.AreEqual(termExpectedCounts[CP_B], result.Value);
+            foreach (LabelAndValue labelValue in result.LabelValues)
             {
                 Assert.AreEqual(termExpectedCounts[CP_B + "/" + labelValue.label], labelValue.value);
             }
@@ -321,9 +321,9 @@ namespace Lucene.Net.Facet.Taxonomy
             Facets facets = GetTaxonomyFacetCounts(taxoReader, Config, sfc);
 
             FacetResult result = facets.GetTopChildren(NUM_CHILDREN_CP_A, CP_A);
-            Assert.AreEqual(-1, (int)result.value);
+            Assert.AreEqual(-1, (int)result.Value);
             int prevValue = int.MaxValue;
-            foreach (LabelAndValue labelValue in result.labelValues)
+            foreach (LabelAndValue labelValue in result.LabelValues)
             {
                 Assert.AreEqual(allExpectedCounts[CP_A + "/" + labelValue.label], labelValue.value);
                 Assert.True((int)labelValue.value <= prevValue, "wrong sort order of sub results: labelValue.value=" + labelValue.value + " prevValue=" + prevValue);
@@ -331,9 +331,9 @@ namespace Lucene.Net.Facet.Taxonomy
             }
 
             result = facets.GetTopChildren(NUM_CHILDREN_CP_B, CP_B);
-            Assert.AreEqual(allExpectedCounts[CP_B], result.value);
+            Assert.AreEqual(allExpectedCounts[CP_B], result.Value);
             prevValue = int.MaxValue;
-            foreach (LabelAndValue labelValue in result.labelValues)
+            foreach (LabelAndValue labelValue in result.LabelValues)
             {
                 Assert.AreEqual(allExpectedCounts[CP_B + "/" + labelValue.label], labelValue.value);
                 Assert.True((int)labelValue.value <= prevValue, "wrong sort order of sub results: labelValue.value=" + labelValue.value + " prevValue=" + prevValue);
@@ -356,14 +356,14 @@ namespace Lucene.Net.Facet.Taxonomy
             Facets facets = GetTaxonomyFacetCounts(taxoReader, Config, sfc);
 
             FacetResult result = facets.GetTopChildren(int.MaxValue, CP_A);
-            Assert.AreEqual(-1, (int)result.value);
-            foreach (LabelAndValue labelValue in result.labelValues)
+            Assert.AreEqual(-1, (int)result.Value);
+            foreach (LabelAndValue labelValue in result.LabelValues)
             {
                 Assert.AreEqual(allExpectedCounts[CP_A + "/" + labelValue.label], labelValue.value);
             }
             result = facets.GetTopChildren(int.MaxValue, CP_B);
-            Assert.AreEqual(allExpectedCounts[CP_B], result.value);
-            foreach (LabelAndValue labelValue in result.labelValues)
+            Assert.AreEqual(allExpectedCounts[CP_B], result.Value);
+            foreach (LabelAndValue labelValue in result.LabelValues)
             {
                 Assert.AreEqual(allExpectedCounts[CP_B + "/" + labelValue.label], labelValue.value);
             }
@@ -384,14 +384,14 @@ namespace Lucene.Net.Facet.Taxonomy
             Facets facets = GetTaxonomyFacetCounts(taxoReader, Config, sfc);
 
             FacetResult result = facets.GetTopChildren(NUM_CHILDREN_CP_C, CP_C);
-            Assert.AreEqual(allExpectedCounts[CP_C], result.value);
-            foreach (LabelAndValue labelValue in result.labelValues)
+            Assert.AreEqual(allExpectedCounts[CP_C], result.Value);
+            foreach (LabelAndValue labelValue in result.LabelValues)
             {
                 Assert.AreEqual(allExpectedCounts[CP_C + "/" + labelValue.label], labelValue.value);
             }
             result = facets.GetTopChildren(NUM_CHILDREN_CP_D, CP_D);
-            Assert.AreEqual(allExpectedCounts[CP_C], result.value);
-            foreach (LabelAndValue labelValue in result.labelValues)
+            Assert.AreEqual(allExpectedCounts[CP_C], result.Value);
+            foreach (LabelAndValue labelValue in result.LabelValues)
             {
                 Assert.AreEqual(allExpectedCounts[CP_D + "/" + labelValue.label], labelValue.value);
             }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetSumValueSource.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetSumValueSource.cs b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetSumValueSource.cs
index f90b9f6..d425a1a 100644
--- a/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetSumValueSource.cs
+++ b/src/Lucene.Net.Tests/core/Facet/Taxonomy/TestTaxonomyFacetSumValueSource.cs
@@ -486,8 +486,8 @@ namespace Lucene.Net.Facet.Taxonomy
             Facets facets1 = GetTaxonomyFacetCounts(taxoReader, config, fc);
             Facets facets2 = new TaxonomyFacetSumValueSource(new DocValuesOrdinalsReader("$b"), taxoReader, config, fc, new TaxonomyFacetSumValueSource.ScoreValueSource());
 
-            Assert.AreEqual(r.MaxDoc, (int)facets1.GetTopChildren(10, "a").value);
-            Assert.AreEqual(r.MaxDoc, (double)facets2.GetTopChildren(10, "b").value, 1E-10);
+            Assert.AreEqual(r.MaxDoc, (int)facets1.GetTopChildren(10, "a").Value);
+            Assert.AreEqual(r.MaxDoc, (double)facets2.GetTopChildren(10, "b").Value, 1E-10);
             IOUtils.Close(taxoWriter, iw, taxoReader, taxoDir, r, indexDir);
         }
 

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/src/Lucene.Net.Tests/core/Facet/TestDrillDownQuery.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/TestDrillDownQuery.cs b/src/Lucene.Net.Tests/core/Facet/TestDrillDownQuery.cs
index 1947b8b..49ce90a 100644
--- a/src/Lucene.Net.Tests/core/Facet/TestDrillDownQuery.cs
+++ b/src/Lucene.Net.Tests/core/Facet/TestDrillDownQuery.cs
@@ -248,11 +248,11 @@ namespace Lucene.Net.Facet
         [Test]
         public virtual void TestTermNonDefault()
         {
-            string aField = config.GetDimConfig("a").indexFieldName;
+            string aField = config.GetDimConfig("a").IndexFieldName;
             Term termA = DrillDownQuery.Term(aField, "a");
             Assert.AreEqual(new Term(aField, "a"), termA);
 
-            string bField = config.GetDimConfig("b").indexFieldName;
+            string bField = config.GetDimConfig("b").IndexFieldName;
             Term termB = DrillDownQuery.Term(bField, "b");
             Assert.AreEqual(new Term(bField, "b"), termB);
         }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/src/Lucene.Net.Tests/core/Facet/TestDrillSideways.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/TestDrillSideways.cs b/src/Lucene.Net.Tests/core/Facet/TestDrillSideways.cs
index 14b3e12..00cb772 100644
--- a/src/Lucene.Net.Tests/core/Facet/TestDrillSideways.cs
+++ b/src/Lucene.Net.Tests/core/Facet/TestDrillSideways.cs
@@ -312,7 +312,7 @@ namespace Lucene.Net.Facet
 
             // Writes facet ords to a separate directory from the
             // main index:
-            DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir, IndexWriterConfig.OpenMode_e.CREATE);
+            var taxoWriter = new DirectoryTaxonomyWriter(taxoDir, IndexWriterConfig.OpenMode_e.CREATE);
 
             FacetsConfig config = new FacetsConfig();
             config.SetHierarchical("dim", true);
@@ -1211,7 +1211,7 @@ namespace Lucene.Net.Facet
 
                 if (fr != null)
                 {
-                    foreach (LabelAndValue labelValue in fr.labelValues)
+                    foreach (LabelAndValue labelValue in fr.LabelValues)
                     {
                         actualValues[labelValue.label] = (int)labelValue.value;
                         if (VERBOSE)
@@ -1220,7 +1220,7 @@ namespace Lucene.Net.Facet
                             idx++;
                         }
                     }
-                    Assert.AreEqual(expected.UniqueCounts[dim], fr.childCount, "dim=" + dim);
+                    Assert.AreEqual(expected.UniqueCounts[dim], fr.ChildCount, "dim=" + dim);
                 }
 
                 if (topN < dimValues[dim].Length)
@@ -1245,7 +1245,7 @@ namespace Lucene.Net.Facet
 
                     if (fr != null)
                     {
-                        Assert.AreEqual(topNIDs.Length, fr.labelValues.Length);
+                        Assert.AreEqual(topNIDs.Length, fr.LabelValues.Length);
                     }
                     else
                     {
@@ -1254,12 +1254,12 @@ namespace Lucene.Net.Facet
                     for (int i = 0; i < topNIDs.Length; i++)
                     {
                         int expectedOrd = topNIDs[i];
-                        Assert.AreEqual(expected.Counts[dim][expectedOrd], (int)fr.labelValues[i].value);
+                        Assert.AreEqual(expected.Counts[dim][expectedOrd], (int)fr.LabelValues[i].value);
                         if (isSortedSetDV)
                         {
                             // Tie-break facet labels are only in unicode
                             // order with SortedSetDVFacets:
-                            Assert.AreEqual("value @ idx=" + i, dimValues[dim][expectedOrd], fr.labelValues[i].label);
+                            Assert.AreEqual("value @ idx=" + i, dimValues[dim][expectedOrd], fr.LabelValues[i].label);
                         }
                     }
                 }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/src/Lucene.Net.Tests/core/Facet/TestFacetsConfig.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/TestFacetsConfig.cs b/src/Lucene.Net.Tests/core/Facet/TestFacetsConfig.cs
index f9de7c7..9c14f79 100644
--- a/src/Lucene.Net.Tests/core/Facet/TestFacetsConfig.cs
+++ b/src/Lucene.Net.Tests/core/Facet/TestFacetsConfig.cs
@@ -89,8 +89,8 @@ namespace Lucene.Net.Facet
 
             Facets facets = GetTaxonomyFacetCounts(taxoReader, facetsConfig, fc);
             FacetResult res = facets.GetTopChildren(10, "a");
-            Assert.AreEqual(1, res.labelValues.Length);
-            Assert.AreEqual(2, res.labelValues[0].value);
+            Assert.AreEqual(1, res.LabelValues.Length);
+            Assert.AreEqual(2, res.LabelValues[0].value);
             IOUtils.Close(indexReader, taxoReader);
 
             IOUtils.Close(indexDir, taxoDir);
@@ -104,7 +104,7 @@ namespace Lucene.Net.Facet
         {
             FacetsConfig config = new FacetsConfigAnonymousInnerClassHelper(this);
 
-            Assert.True(config.GetDimConfig("foobar").hierarchical);
+            Assert.True(config.GetDimConfig("foobar").Hierarchical);
         }
 
         private class FacetsConfigAnonymousInnerClassHelper : FacetsConfig
@@ -121,7 +121,7 @@ namespace Lucene.Net.Facet
                 get
                 {
                     DimConfig config = new DimConfig();
-                    config.hierarchical = true;
+                    config.Hierarchical = true;
                     return config;
                 }
             }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/src/Lucene.Net.Tests/core/Facet/TestMultipleIndexFields.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/TestMultipleIndexFields.cs b/src/Lucene.Net.Tests/core/Facet/TestMultipleIndexFields.cs
index 490836b..086e608 100644
--- a/src/Lucene.Net.Tests/core/Facet/TestMultipleIndexFields.cs
+++ b/src/Lucene.Net.Tests/core/Facet/TestMultipleIndexFields.cs
@@ -79,7 +79,7 @@ namespace Lucene.Net.Facet
             // prepare searcher to search against
             IndexSearcher searcher = NewSearcher(ir);
 
-            FacetsCollector sfc = performSearch(tr, ir, searcher);
+            FacetsCollector sfc = PerformSearch(tr, ir, searcher);
 
             // Obtain facets results and hand-test them
             AssertCorrectResults(GetTaxonomyFacetCounts(tr, config, sfc));
@@ -98,7 +98,7 @@ namespace Lucene.Net.Facet
             // create and open an index writer
             RandomIndexWriter iw = new RandomIndexWriter(Random(), indexDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random(), MockTokenizer.WHITESPACE, false)));
             // create and open a taxonomy writer
-            TaxonomyWriter tw = new DirectoryTaxonomyWriter(taxoDir, OpenMode.CREATE);
+            var tw = new DirectoryTaxonomyWriter(taxoDir, OpenMode.CREATE);
 
             FacetsConfig config = Config;
             config.SetIndexFieldName("Author", "$author");
@@ -113,7 +113,7 @@ namespace Lucene.Net.Facet
             // prepare searcher to search against
             IndexSearcher searcher = NewSearcher(ir);
 
-            FacetsCollector sfc = performSearch(tr, ir, searcher);
+            FacetsCollector sfc = PerformSearch(tr, ir, searcher);
 
             IDictionary<string, Facets> facetsMap = new Dictionary<string, Facets>();
             facetsMap["Author"] = GetTaxonomyFacetCounts(tr, config, sfc, "$author");
@@ -153,7 +153,7 @@ namespace Lucene.Net.Facet
             // prepare searcher to search against
             IndexSearcher searcher = NewSearcher(ir);
 
-            FacetsCollector sfc = performSearch(tr, ir, searcher);
+            FacetsCollector sfc = PerformSearch(tr, ir, searcher);
 
             IDictionary<string, Facets> facetsMap = new Dictionary<string, Facets>();
             Facets facets2 = GetTaxonomyFacetCounts(tr, config, sfc, "$music");
@@ -209,7 +209,7 @@ namespace Lucene.Net.Facet
             // prepare searcher to search against
             IndexSearcher searcher = NewSearcher(ir);
 
-            FacetsCollector sfc = performSearch(tr, ir, searcher);
+            FacetsCollector sfc = PerformSearch(tr, ir, searcher);
 
             IDictionary<string, Facets> facetsMap = new Dictionary<string, Facets>();
             facetsMap["Band"] = GetTaxonomyFacetCounts(tr, config, sfc, "$bands");
@@ -251,7 +251,7 @@ namespace Lucene.Net.Facet
             // prepare searcher to search against
             IndexSearcher searcher = NewSearcher(ir);
 
-            FacetsCollector sfc = performSearch(tr, ir, searcher);
+            FacetsCollector sfc = PerformSearch(tr, ir, searcher);
 
             IDictionary<string, Facets> facetsMap = new Dictionary<string, Facets>();
             Facets facets2 = GetTaxonomyFacetCounts(tr, config, sfc, "$music");
@@ -279,7 +279,7 @@ namespace Lucene.Net.Facet
         }
 
         
-        private static FacetsCollector performSearch(TaxonomyReader tr, IndexReader ir, IndexSearcher searcher)
+        private static FacetsCollector PerformSearch(TaxonomyReader tr, IndexReader ir, IndexSearcher searcher)
         {
             FacetsCollector fc = new FacetsCollector();
             FacetsCollector.Search(searcher, new MatchAllDocsQuery(), 10, fc);

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/f9ee1240/src/Lucene.Net.Tests/core/Facet/TestRandomSamplingFacetsCollector.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Facet/TestRandomSamplingFacetsCollector.cs b/src/Lucene.Net.Tests/core/Facet/TestRandomSamplingFacetsCollector.cs
index 3ada2c5..afd46e5 100644
--- a/src/Lucene.Net.Tests/core/Facet/TestRandomSamplingFacetsCollector.cs
+++ b/src/Lucene.Net.Tests/core/Facet/TestRandomSamplingFacetsCollector.cs
@@ -116,21 +116,21 @@ namespace Lucene.Net.Facet
 
             // we should have five children, but there is a small chance we have less.
             // (see above).
-            Assert.True(random10Result.childCount <= maxNumChildren);
+            Assert.True(random10Result.ChildCount <= maxNumChildren);
             // there should be one child at least.
-            Assert.True(random10Result.childCount >= 1);
+            Assert.True(random10Result.ChildCount >= 1);
 
             // now calculate some statistics to determine if the sampled result is 'ok'.
             // because random sampling is used, the results will vary each time.
             int sum = 0;
-            foreach (LabelAndValue lav in random10Result.labelValues)
+            foreach (LabelAndValue lav in random10Result.LabelValues)
             {
                 sum += (int)lav.value;
             }
             float mu = (float)sum / (float)maxNumChildren;
 
             float variance = 0;
-            foreach (LabelAndValue lav in random10Result.labelValues)
+            foreach (LabelAndValue lav in random10Result.LabelValues)
             {
                 variance += (float)Math.Pow((mu - (int)lav.value), 2);
             }


[05/26] lucenenet git commit: fix build

Posted by sy...@apache.org.
fix build


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

Branch: refs/heads/master
Commit: e4e81d0c3feb7ff7762d36c978942b8746bf5f42
Parents: 931d42e
Author: eladmarg <el...@gmail.com>
Authored: Sat Nov 8 18:08:43 2014 -0800
Committer: eladmarg <el...@gmail.com>
Committed: Sat Nov 8 18:08:43 2014 -0800

----------------------------------------------------------------------
 src/Lucene.Net.Core/Analysis/Analyzer.cs            |  2 +-
 src/Lucene.Net.Core/Analysis/AnalyzerWrapper.cs     |  8 +++++---
 .../Analysis/MockAnalyzer.cs                        |  2 +-
 .../Analysis/MockBytesAnalyzer.cs                   |  2 +-
 .../Analysis/MockPayloadAnalyzer.cs                 |  2 +-
 .../Analysis/MockTokenizer.cs                       |  6 +++---
 .../core/Analysis/TestGraphTokenizers.cs            | 16 ++++++++--------
 .../core/Analysis/TestLookaheadTokenFilter.cs       |  6 +++---
 .../core/Analysis/TestMockAnalyzer.cs               |  4 ++--
 .../core/Analysis/TestMockCharFilter.cs             |  2 +-
 .../Codecs/Lucene41/TestBlockPostingsFormat3.cs     |  2 +-
 .../core/Index/TestDocInverterPerFieldErrorInfo.cs  |  2 +-
 .../core/Index/TestDocumentWriter.cs                |  4 ++--
 src/Lucene.Net.Tests/core/Index/TestIndexWriter.cs  |  8 ++++----
 .../core/Index/TestIndexWriterCommit.cs             |  4 ++--
 .../core/Index/TestIndexWriterDelete.cs             |  2 +-
 .../core/Index/TestIndexWriterExceptions.cs         |  8 ++++----
 .../core/Index/TestLazyProxSkipping.cs              |  2 +-
 .../core/Index/TestMultiLevelSkipList.cs            |  2 +-
 src/Lucene.Net.Tests/core/Index/TestPayloads.cs     |  2 +-
 .../core/Index/TestTermVectorsReader.cs             |  2 +-
 src/Lucene.Net.Tests/core/Index/TestTermdocPerf.cs  |  2 +-
 .../core/Search/FuzzyTermOnShortTermsTest.cs        |  2 +-
 .../core/Search/Payloads/PayloadHelper.cs           |  2 +-
 .../core/Search/Payloads/TestPayloadNearQuery.cs    |  2 +-
 .../core/Search/Payloads/TestPayloadTermQuery.cs    |  2 +-
 .../core/Search/Spans/TestBasics.cs                 |  2 +-
 .../core/Search/Spans/TestPayloadSpans.cs           |  4 ++--
 src/Lucene.Net.Tests/core/Search/TestPhraseQuery.cs |  2 +-
 .../core/Search/TestPositionIncrement.cs            |  2 +-
 .../core/Search/TestTermRangeQuery.cs               |  2 +-
 src/Lucene.Net.Tests/core/Util/TestQueryBuilder.cs  |  6 +++---
 32 files changed, 59 insertions(+), 57 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Core/Analysis/Analyzer.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Analysis/Analyzer.cs b/src/Lucene.Net.Core/Analysis/Analyzer.cs
index 0ae0c8e..c183885 100644
--- a/src/Lucene.Net.Core/Analysis/Analyzer.cs
+++ b/src/Lucene.Net.Core/Analysis/Analyzer.cs
@@ -104,7 +104,7 @@ namespace Lucene.Net.Analysis
         /// <param name="reader">
         ///          the reader passed to the <seealso cref="Tokenizer"/> constructor </param>
         /// <returns> the <seealso cref="TokenStreamComponents"/> for this analyzer. </returns>
-        protected abstract TokenStreamComponents CreateComponents(string fieldName, TextReader reader);
+        public abstract TokenStreamComponents CreateComponents(string fieldName, TextReader reader);
 
         /// <summary>
         /// Returns a TokenStream suitable for <code>fieldName</code>, tokenizing

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Core/Analysis/AnalyzerWrapper.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Analysis/AnalyzerWrapper.cs b/src/Lucene.Net.Core/Analysis/AnalyzerWrapper.cs
index f58467b..6d58110 100644
--- a/src/Lucene.Net.Core/Analysis/AnalyzerWrapper.cs
+++ b/src/Lucene.Net.Core/Analysis/AnalyzerWrapper.cs
@@ -90,14 +90,16 @@ namespace Lucene.Net.Analysis
         /// <param name="reader">
         ///          the reader to wrap </param>
         /// <returns> the wrapped reader </returns>
-        protected internal virtual TextReader WrapReader(string fieldName, TextReader reader)
+        protected virtual TextReader WrapReader(string fieldName, TextReader reader)
         {
             return reader;
         }
 
-        protected override sealed TokenStreamComponents CreateComponents(string fieldName, TextReader aReader)
+        public override sealed TokenStreamComponents CreateComponents(string fieldName, TextReader aReader)
         {
-            return WrapComponents(fieldName, GetWrappedAnalyzer(fieldName).CreateComponents(fieldName, aReader));
+            var wrappedAnalyzer = GetWrappedAnalyzer(fieldName);
+            var component = wrappedAnalyzer.CreateComponents(fieldName, aReader);
+            return WrapComponents(fieldName, component);
         }
 
         public override int GetPositionIncrementGap(string fieldName)

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.TestFramework/Analysis/MockAnalyzer.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.TestFramework/Analysis/MockAnalyzer.cs b/src/Lucene.Net.TestFramework/Analysis/MockAnalyzer.cs
index 37fe8cd..9a1b883 100644
--- a/src/Lucene.Net.TestFramework/Analysis/MockAnalyzer.cs
+++ b/src/Lucene.Net.TestFramework/Analysis/MockAnalyzer.cs
@@ -89,7 +89,7 @@ namespace Lucene.Net.Analysis
         {
         }
 
-        protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+        public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
         {
             MockTokenizer tokenizer = new MockTokenizer(reader, RunAutomaton, LowerCase, MaxTokenLength_Renamed);
             tokenizer.EnableChecks = EnableChecks_Renamed;

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.TestFramework/Analysis/MockBytesAnalyzer.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.TestFramework/Analysis/MockBytesAnalyzer.cs b/src/Lucene.Net.TestFramework/Analysis/MockBytesAnalyzer.cs
index 12934df..6967b75 100644
--- a/src/Lucene.Net.TestFramework/Analysis/MockBytesAnalyzer.cs
+++ b/src/Lucene.Net.TestFramework/Analysis/MockBytesAnalyzer.cs
@@ -26,7 +26,7 @@ namespace Lucene.Net.Analysis
     {
         private readonly MockBytesAttributeFactory Factory = new MockBytesAttributeFactory();
 
-        protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+        public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
         {
             Tokenizer t = new MockTokenizer(Factory, reader, MockTokenizer.KEYWORD, false, MockTokenizer.DEFAULT_MAX_TOKEN_LENGTH);
             return new TokenStreamComponents(t);

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.TestFramework/Analysis/MockPayloadAnalyzer.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.TestFramework/Analysis/MockPayloadAnalyzer.cs b/src/Lucene.Net.TestFramework/Analysis/MockPayloadAnalyzer.cs
index 60ee80e..29f783e 100644
--- a/src/Lucene.Net.TestFramework/Analysis/MockPayloadAnalyzer.cs
+++ b/src/Lucene.Net.TestFramework/Analysis/MockPayloadAnalyzer.cs
@@ -31,7 +31,7 @@ namespace Lucene.Net.Analysis
     /// </summary>
     public sealed class MockPayloadAnalyzer : Analyzer
     {
-        protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+        public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
         {
             Tokenizer result = new MockTokenizer(reader, MockTokenizer.WHITESPACE, true);
             return new TokenStreamComponents(result, new MockPayloadFilter(result, fieldName));

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.TestFramework/Analysis/MockTokenizer.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.TestFramework/Analysis/MockTokenizer.cs b/src/Lucene.Net.TestFramework/Analysis/MockTokenizer.cs
index 5fa4ae3..7cb9f28 100644
--- a/src/Lucene.Net.TestFramework/Analysis/MockTokenizer.cs
+++ b/src/Lucene.Net.TestFramework/Analysis/MockTokenizer.cs
@@ -247,14 +247,14 @@ namespace Lucene.Net.Analysis
                     {
                         // read(char[])
                         char[] c = new char[1];
-                        int ret = Input.Read(c, 0, c.Length);
+                        int ret = input.Read(c, 0, c.Length);
                         return ret == 0 ? -1 : c[0];
                     }
                 case 1:
                     {
                         // read(char[], int, int)
                         char[] c = new char[2];
-                        int ret = Input.Read(c, 1, 1);
+                        int ret = input.Read(c, 1, 1);
                         return ret == 0 ? -1 : c[1];
                     }
                 /* LUCENE TO-DO not sure if needed, CharBuffer not supported
@@ -268,7 +268,7 @@ namespace Lucene.Net.Analysis
                   }*/
                 default:
                     // read()
-                    return Input.Read();
+                    return input.Read();
             }
         }
 

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Analysis/TestGraphTokenizers.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Analysis/TestGraphTokenizers.cs b/src/Lucene.Net.Tests/core/Analysis/TestGraphTokenizers.cs
index 4fe19b1..e09f68b 100644
--- a/src/Lucene.Net.Tests/core/Analysis/TestGraphTokenizers.cs
+++ b/src/Lucene.Net.Tests/core/Analysis/TestGraphTokenizers.cs
@@ -204,7 +204,7 @@ namespace Lucene.Net.Analysis
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer t = new MockTokenizer(reader, MockTokenizer.WHITESPACE, false);
                 TokenStream t2 = new MockGraphTokenFilter(Random(), t);
@@ -239,7 +239,7 @@ namespace Lucene.Net.Analysis
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer t = new GraphTokenizer(reader);
                 TokenStream t2 = new MockGraphTokenFilter(Random(), t);
@@ -328,7 +328,7 @@ namespace Lucene.Net.Analysis
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer t = new MockTokenizer(reader, MockTokenizer.WHITESPACE, false);
                 TokenStream t2 = new MockGraphTokenFilter(Random(), t);
@@ -368,7 +368,7 @@ namespace Lucene.Net.Analysis
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer t = new MockTokenizer(reader, MockTokenizer.WHITESPACE, false);
                 TokenStream t2 = new RemoveATokens(t);
@@ -405,7 +405,7 @@ namespace Lucene.Net.Analysis
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer t = new MockTokenizer(reader, MockTokenizer.WHITESPACE, false);
                 TokenStream t2 = new MockGraphTokenFilter(Random(), t);
@@ -449,7 +449,7 @@ namespace Lucene.Net.Analysis
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer t = new MockTokenizer(reader, MockTokenizer.WHITESPACE, false);
                 TokenStream t1 = new MockGraphTokenFilter(Random(), t);
@@ -486,7 +486,7 @@ namespace Lucene.Net.Analysis
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer t = new MockTokenizer(reader, MockTokenizer.WHITESPACE, false);
                 TokenStream t1 = new MockGraphTokenFilter(Random(), t);
@@ -523,7 +523,7 @@ namespace Lucene.Net.Analysis
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer t = new MockTokenizer(reader, MockTokenizer.WHITESPACE, false);
                 TokenStream t1 = new MockHoleInjectingTokenFilter(Random(), t);

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Analysis/TestLookaheadTokenFilter.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Analysis/TestLookaheadTokenFilter.cs b/src/Lucene.Net.Tests/core/Analysis/TestLookaheadTokenFilter.cs
index db48b98..04622b8 100644
--- a/src/Lucene.Net.Tests/core/Analysis/TestLookaheadTokenFilter.cs
+++ b/src/Lucene.Net.Tests/core/Analysis/TestLookaheadTokenFilter.cs
@@ -41,7 +41,7 @@ namespace Lucene.Net.Analysis
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Random random = Random();
                 Tokenizer tokenizer = new MockTokenizer(reader, MockTokenizer.WHITESPACE, random.NextBoolean());
@@ -84,7 +84,7 @@ namespace Lucene.Net.Analysis
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer tokenizer = new MockTokenizer(reader, MockTokenizer.WHITESPACE, Random().NextBoolean());
                 TokenStream output = new NeverPeeksLookaheadTokenFilter(tokenizer);
@@ -109,7 +109,7 @@ namespace Lucene.Net.Analysis
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer source = new MockTokenizer(reader, MockTokenizer.WHITESPACE, false);
                 TrivialLookaheadFilter filter = new TrivialLookaheadFilter(source);

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Analysis/TestMockAnalyzer.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Analysis/TestMockAnalyzer.cs b/src/Lucene.Net.Tests/core/Analysis/TestMockAnalyzer.cs
index e39be6a..e185484 100644
--- a/src/Lucene.Net.Tests/core/Analysis/TestMockAnalyzer.cs
+++ b/src/Lucene.Net.Tests/core/Analysis/TestMockAnalyzer.cs
@@ -182,7 +182,7 @@ namespace Lucene.Net.Analysis
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer t = new MockTokenizer(reader, MockTokenizer.WHITESPACE, false, 5);
                 return new TokenStreamComponents(t, t);
@@ -259,7 +259,7 @@ namespace Lucene.Net.Analysis
                 this.Limit = limit;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer t = new MockTokenizer(reader, Dfa, Lowercase, Limit);
                 return new TokenStreamComponents(t, t);

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Analysis/TestMockCharFilter.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Analysis/TestMockCharFilter.cs b/src/Lucene.Net.Tests/core/Analysis/TestMockCharFilter.cs
index cabd3f2..03df580 100644
--- a/src/Lucene.Net.Tests/core/Analysis/TestMockCharFilter.cs
+++ b/src/Lucene.Net.Tests/core/Analysis/TestMockCharFilter.cs
@@ -44,7 +44,7 @@ namespace Lucene.Net.Analysis
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer tokenizer = new MockTokenizer(reader, MockTokenizer.WHITESPACE, false);
                 return new TokenStreamComponents(tokenizer, tokenizer);

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Codecs/Lucene41/TestBlockPostingsFormat3.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Codecs/Lucene41/TestBlockPostingsFormat3.cs b/src/Lucene.Net.Tests/core/Codecs/Lucene41/TestBlockPostingsFormat3.cs
index ca67a17..138d941 100644
--- a/src/Lucene.Net.Tests/core/Codecs/Lucene41/TestBlockPostingsFormat3.cs
+++ b/src/Lucene.Net.Tests/core/Codecs/Lucene41/TestBlockPostingsFormat3.cs
@@ -150,7 +150,7 @@ namespace Lucene.Net.Codecs.Lucene41
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer tokenizer = new MockTokenizer(reader);
                 if (fieldName.Contains("payloadsFixed"))

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Index/TestDocInverterPerFieldErrorInfo.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Index/TestDocInverterPerFieldErrorInfo.cs b/src/Lucene.Net.Tests/core/Index/TestDocInverterPerFieldErrorInfo.cs
index e2eb471..8ec9e62 100644
--- a/src/Lucene.Net.Tests/core/Index/TestDocInverterPerFieldErrorInfo.cs
+++ b/src/Lucene.Net.Tests/core/Index/TestDocInverterPerFieldErrorInfo.cs
@@ -54,7 +54,7 @@ namespace Lucene.Net.Index
 
         private class ThrowingAnalyzer : Analyzer
         {
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader input)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader input)
             {
                 Tokenizer tokenizer = new MockTokenizer(input);
                 if (fieldName.Equals("distinctiveFieldName"))

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Index/TestDocumentWriter.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Index/TestDocumentWriter.cs b/src/Lucene.Net.Tests/core/Index/TestDocumentWriter.cs
index e66a2fe..f055b36 100644
--- a/src/Lucene.Net.Tests/core/Index/TestDocumentWriter.cs
+++ b/src/Lucene.Net.Tests/core/Index/TestDocumentWriter.cs
@@ -146,7 +146,7 @@ namespace Lucene.Net.Index
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 return new TokenStreamComponents(new MockTokenizer(reader, MockTokenizer.WHITESPACE, false));
             }
@@ -195,7 +195,7 @@ namespace Lucene.Net.Index
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer tokenizer = new MockTokenizer(reader, MockTokenizer.WHITESPACE, false);
                 return new TokenStreamComponents(tokenizer, new TokenFilterAnonymousInnerClassHelper(this, tokenizer));

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Index/TestIndexWriter.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Index/TestIndexWriter.cs b/src/Lucene.Net.Tests/core/Index/TestIndexWriter.cs
index a9801f3..8631da7 100644
--- a/src/Lucene.Net.Tests/core/Index/TestIndexWriter.cs
+++ b/src/Lucene.Net.Tests/core/Index/TestIndexWriter.cs
@@ -1866,7 +1866,7 @@ namespace Lucene.Net.Index
 
         internal sealed class StringSplitAnalyzer : Analyzer
         {
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 return new TokenStreamComponents(new StringSplitTokenizer(reader));
             }
@@ -2226,7 +2226,7 @@ namespace Lucene.Net.Index
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 throw new InvalidOperationException("don't invoke me!");
             }
@@ -2301,7 +2301,7 @@ namespace Lucene.Net.Index
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer tokenizer = new MockTokenizer(reader);
                 TokenStream stream = new MockTokenFilter(tokenizer, MockTokenFilter.ENGLISH_STOPSET);
@@ -2346,7 +2346,7 @@ namespace Lucene.Net.Index
                 this.SecondSet = secondSet;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer tokenizer = new MockTokenizer(reader);
                 TokenStream stream = new MockTokenFilter(tokenizer, MockTokenFilter.ENGLISH_STOPSET);

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Index/TestIndexWriterCommit.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Index/TestIndexWriterCommit.cs b/src/Lucene.Net.Tests/core/Index/TestIndexWriterCommit.cs
index 1a4b4d3..4580112 100644
--- a/src/Lucene.Net.Tests/core/Index/TestIndexWriterCommit.cs
+++ b/src/Lucene.Net.Tests/core/Index/TestIndexWriterCommit.cs
@@ -258,7 +258,7 @@ namespace Lucene.Net.Index
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 return new TokenStreamComponents(new MockTokenizer(reader, MockTokenizer.WHITESPACE, true));
             }
@@ -276,7 +276,7 @@ namespace Lucene.Net.Index
                 this.Length = length;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer tokenizer = new MockTokenizer(reader, MockTokenizer.WHITESPACE, true);
                 return new TokenStreamComponents(tokenizer, new MockFixedLengthPayloadFilter(Random(), tokenizer, Length));

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Index/TestIndexWriterDelete.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Index/TestIndexWriterDelete.cs b/src/Lucene.Net.Tests/core/Index/TestIndexWriterDelete.cs
index d5f8513..60dff7e 100644
--- a/src/Lucene.Net.Tests/core/Index/TestIndexWriterDelete.cs
+++ b/src/Lucene.Net.Tests/core/Index/TestIndexWriterDelete.cs
@@ -1192,7 +1192,7 @@ namespace Lucene.Net.Index
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 return new TokenStreamComponents(new MockTokenizer(reader, MockTokenizer.WHITESPACE, true));
             }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Index/TestIndexWriterExceptions.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Index/TestIndexWriterExceptions.cs b/src/Lucene.Net.Tests/core/Index/TestIndexWriterExceptions.cs
index 58b706e..3aa3df3 100644
--- a/src/Lucene.Net.Tests/core/Index/TestIndexWriterExceptions.cs
+++ b/src/Lucene.Net.Tests/core/Index/TestIndexWriterExceptions.cs
@@ -519,7 +519,7 @@ namespace Lucene.Net.Index
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 MockTokenizer tokenizer = new MockTokenizer(reader, MockTokenizer.WHITESPACE, false);
                 tokenizer.EnableChecks = false; // disable workflow checking as we forcefully close() in exceptional cases.
@@ -637,7 +637,7 @@ namespace Lucene.Net.Index
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 MockTokenizer tokenizer = new MockTokenizer(reader, MockTokenizer.SIMPLE, true);
                 tokenizer.EnableChecks = false; // disable workflow checking as we forcefully close() in exceptional cases.
@@ -873,7 +873,7 @@ namespace Lucene.Net.Index
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 MockTokenizer tokenizer = new MockTokenizer(reader, MockTokenizer.WHITESPACE, false);
                 tokenizer.EnableChecks = false; // disable workflow checking as we forcefully close() in exceptional cases.
@@ -972,7 +972,7 @@ namespace Lucene.Net.Index
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 MockTokenizer tokenizer = new MockTokenizer(reader, MockTokenizer.WHITESPACE, false);
                 tokenizer.EnableChecks = false; // disable workflow checking as we forcefully close() in exceptional cases.

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Index/TestLazyProxSkipping.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Index/TestLazyProxSkipping.cs b/src/Lucene.Net.Tests/core/Index/TestLazyProxSkipping.cs
index 59e3e02..4010ec4 100644
--- a/src/Lucene.Net.Tests/core/Index/TestLazyProxSkipping.cs
+++ b/src/Lucene.Net.Tests/core/Index/TestLazyProxSkipping.cs
@@ -121,7 +121,7 @@ namespace Lucene.Net.Index
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 return new TokenStreamComponents(new MockTokenizer(reader, MockTokenizer.WHITESPACE, true));
             }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Index/TestMultiLevelSkipList.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Index/TestMultiLevelSkipList.cs b/src/Lucene.Net.Tests/core/Index/TestMultiLevelSkipList.cs
index 3caf34c..4a7c39e 100644
--- a/src/Lucene.Net.Tests/core/Index/TestMultiLevelSkipList.cs
+++ b/src/Lucene.Net.Tests/core/Index/TestMultiLevelSkipList.cs
@@ -127,7 +127,7 @@ namespace Lucene.Net.Index
         {
             internal readonly AtomicInteger PayloadCount = new AtomicInteger(-1);
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer tokenizer = new MockTokenizer(reader, MockTokenizer.WHITESPACE, true);
                 return new TokenStreamComponents(tokenizer, new PayloadFilter(PayloadCount, tokenizer));

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Index/TestPayloads.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Index/TestPayloads.cs b/src/Lucene.Net.Tests/core/Index/TestPayloads.cs
index 903225a..3fc6e51 100644
--- a/src/Lucene.Net.Tests/core/Index/TestPayloads.cs
+++ b/src/Lucene.Net.Tests/core/Index/TestPayloads.cs
@@ -387,7 +387,7 @@ namespace Lucene.Net.Index
                 FieldToData[field] = new PayloadData(data, offset, length);
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 PayloadData payload;
                 FieldToData.TryGetValue(fieldName, out payload);

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Index/TestTermVectorsReader.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Index/TestTermVectorsReader.cs b/src/Lucene.Net.Tests/core/Index/TestTermVectorsReader.cs
index 24deec6..7995318 100644
--- a/src/Lucene.Net.Tests/core/Index/TestTermVectorsReader.cs
+++ b/src/Lucene.Net.Tests/core/Index/TestTermVectorsReader.cs
@@ -228,7 +228,7 @@ namespace Lucene.Net.Index
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 return new TokenStreamComponents(new MyTokenizer(OuterInstance, reader));
             }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Index/TestTermdocPerf.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Index/TestTermdocPerf.cs b/src/Lucene.Net.Tests/core/Index/TestTermdocPerf.cs
index fe83c63..5160a75 100644
--- a/src/Lucene.Net.Tests/core/Index/TestTermdocPerf.cs
+++ b/src/Lucene.Net.Tests/core/Index/TestTermdocPerf.cs
@@ -119,7 +119,7 @@ namespace Lucene.Net.Index
                 this.PercentDocs = percentDocs;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 return new TokenStreamComponents(new RepeatingTokenizer(reader, Val, Random, PercentDocs, MaxTF));
             }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Search/FuzzyTermOnShortTermsTest.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Search/FuzzyTermOnShortTermsTest.cs b/src/Lucene.Net.Tests/core/Search/FuzzyTermOnShortTermsTest.cs
index 1b9fb84..b668aad 100644
--- a/src/Lucene.Net.Tests/core/Search/FuzzyTermOnShortTermsTest.cs
+++ b/src/Lucene.Net.Tests/core/Search/FuzzyTermOnShortTermsTest.cs
@@ -89,7 +89,7 @@ namespace Lucene.Net.Search
             {
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer tokenizer = new MockTokenizer(reader, MockTokenizer.SIMPLE, true);
                 return new TokenStreamComponents(tokenizer, tokenizer);

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Search/Payloads/PayloadHelper.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Search/Payloads/PayloadHelper.cs b/src/Lucene.Net.Tests/core/Search/Payloads/PayloadHelper.cs
index a88b90e..a65d8f2 100644
--- a/src/Lucene.Net.Tests/core/Search/Payloads/PayloadHelper.cs
+++ b/src/Lucene.Net.Tests/core/Search/Payloads/PayloadHelper.cs
@@ -63,7 +63,7 @@ namespace Lucene.Net.Search.Payloads
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer result = new MockTokenizer(reader, MockTokenizer.SIMPLE, true);
                 return new TokenStreamComponents(result, new PayloadFilter(OuterInstance, result, fieldName));

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Search/Payloads/TestPayloadNearQuery.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Search/Payloads/TestPayloadNearQuery.cs b/src/Lucene.Net.Tests/core/Search/Payloads/TestPayloadNearQuery.cs
index 9df0229..4138ed0 100644
--- a/src/Lucene.Net.Tests/core/Search/Payloads/TestPayloadNearQuery.cs
+++ b/src/Lucene.Net.Tests/core/Search/Payloads/TestPayloadNearQuery.cs
@@ -51,7 +51,7 @@ namespace Lucene.Net.Search.Payloads
 
         private class PayloadAnalyzer : Analyzer
         {
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer result = new MockTokenizer(reader, MockTokenizer.SIMPLE, true);
                 return new TokenStreamComponents(result, new PayloadFilter(result, fieldName));

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Search/Payloads/TestPayloadTermQuery.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Search/Payloads/TestPayloadTermQuery.cs b/src/Lucene.Net.Tests/core/Search/Payloads/TestPayloadTermQuery.cs
index 2f0ced7..d2b3ca1 100644
--- a/src/Lucene.Net.Tests/core/Search/Payloads/TestPayloadTermQuery.cs
+++ b/src/Lucene.Net.Tests/core/Search/Payloads/TestPayloadTermQuery.cs
@@ -58,7 +58,7 @@ namespace Lucene.Net.Search.Payloads
             {
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer result = new MockTokenizer(reader, MockTokenizer.SIMPLE, true);
                 return new TokenStreamComponents(result, new PayloadFilter(result, fieldName));

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Search/Spans/TestBasics.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Search/Spans/TestBasics.cs b/src/Lucene.Net.Tests/core/Search/Spans/TestBasics.cs
index 0aef0b3..87cbbc2 100644
--- a/src/Lucene.Net.Tests/core/Search/Spans/TestBasics.cs
+++ b/src/Lucene.Net.Tests/core/Search/Spans/TestBasics.cs
@@ -118,7 +118,7 @@ namespace Lucene.Net.Search.Spans
             {
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer tokenizer = new MockTokenizer(reader, MockTokenizer.SIMPLE, true);
                 return new TokenStreamComponents(tokenizer, new SimplePayloadFilter(tokenizer));

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Search/Spans/TestPayloadSpans.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Search/Spans/TestPayloadSpans.cs b/src/Lucene.Net.Tests/core/Search/Spans/TestPayloadSpans.cs
index 45ce22a..c1cc02e 100644
--- a/src/Lucene.Net.Tests/core/Search/Spans/TestPayloadSpans.cs
+++ b/src/Lucene.Net.Tests/core/Search/Spans/TestPayloadSpans.cs
@@ -510,7 +510,7 @@ namespace Lucene.Net.Search.Spans
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer result = new MockTokenizer(reader, MockTokenizer.SIMPLE, true);
                 return new TokenStreamComponents(result, new PayloadFilter(OuterInstance, result));
@@ -581,7 +581,7 @@ namespace Lucene.Net.Search.Spans
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer result = new MockTokenizer(reader, MockTokenizer.SIMPLE, true);
                 return new TokenStreamComponents(result, new PayloadFilter(OuterInstance, result));

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Search/TestPhraseQuery.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Search/TestPhraseQuery.cs b/src/Lucene.Net.Tests/core/Search/TestPhraseQuery.cs
index 2e1ac1d..de56994 100644
--- a/src/Lucene.Net.Tests/core/Search/TestPhraseQuery.cs
+++ b/src/Lucene.Net.Tests/core/Search/TestPhraseQuery.cs
@@ -90,7 +90,7 @@ namespace Lucene.Net.Search
             {
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 return new TokenStreamComponents(new MockTokenizer(reader, MockTokenizer.WHITESPACE, false));
             }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Search/TestPositionIncrement.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Search/TestPositionIncrement.cs b/src/Lucene.Net.Tests/core/Search/TestPositionIncrement.cs
index 83b0970..982c244 100644
--- a/src/Lucene.Net.Tests/core/Search/TestPositionIncrement.cs
+++ b/src/Lucene.Net.Tests/core/Search/TestPositionIncrement.cs
@@ -173,7 +173,7 @@ namespace Lucene.Net.Search
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 return new TokenStreamComponents(new TokenizerAnonymousInnerClassHelper(this, reader));
             }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Search/TestTermRangeQuery.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Search/TestTermRangeQuery.cs b/src/Lucene.Net.Tests/core/Search/TestTermRangeQuery.cs
index e6541b1..54054e0 100644
--- a/src/Lucene.Net.Tests/core/Search/TestTermRangeQuery.cs
+++ b/src/Lucene.Net.Tests/core/Search/TestTermRangeQuery.cs
@@ -259,7 +259,7 @@ namespace Lucene.Net.Search
                 }
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 return new TokenStreamComponents(new SingleCharTokenizer(reader));
             }

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/e4e81d0c/src/Lucene.Net.Tests/core/Util/TestQueryBuilder.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Util/TestQueryBuilder.cs b/src/Lucene.Net.Tests/core/Util/TestQueryBuilder.cs
index 0140fc0..1c6ee09 100644
--- a/src/Lucene.Net.Tests/core/Util/TestQueryBuilder.cs
+++ b/src/Lucene.Net.Tests/core/Util/TestQueryBuilder.cs
@@ -135,7 +135,7 @@ namespace Lucene.Net.Util
         /// adds synonym of "dog" for "dogs". </summary>
         internal class MockSynonymAnalyzer : Analyzer
         {
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 MockTokenizer tokenizer = new MockTokenizer(reader);
                 return new TokenStreamComponents(tokenizer, new MockSynonymFilter(tokenizer));
@@ -240,7 +240,7 @@ namespace Lucene.Net.Util
                 this.OuterInstance = outerInstance;
             }
 
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 return new TokenStreamComponents(new SimpleCJKTokenizer(reader));
             }
@@ -330,7 +330,7 @@ namespace Lucene.Net.Util
 
         internal class MockCJKSynonymAnalyzer : Analyzer
         {
-            protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
+            public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
             {
                 Tokenizer tokenizer = new SimpleCJKTokenizer(reader);
                 return new TokenStreamComponents(tokenizer, new MockCJKSynonymFilter(tokenizer));