You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucenenet.apache.org by ni...@apache.org on 2016/11/10 11:33:26 UTC

[15/58] [abbrv] lucenenet git commit: Added Core.Support.LinkedHashMap + tests for supporting guaranteed insertion order enumeration of an IDictionary structure.

Added Core.Support.LinkedHashMap + tests for supporting guaranteed insertion order enumeration of an IDictionary structure.


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

Branch: refs/heads/grouping
Commit: 7723e26d74297b714fa3b67cef940f6072322296
Parents: 63fa4ca
Author: Shad Storhaug <sh...@shadstorhaug.com>
Authored: Sat Nov 5 19:52:23 2016 +0700
Committer: Shad Storhaug <sh...@shadstorhaug.com>
Committed: Mon Nov 7 18:44:14 2016 +0700

----------------------------------------------------------------------
 src/Lucene.Net.Core/Lucene.Net.csproj           |   1 +
 src/Lucene.Net.Core/Support/HashMap.cs          |  49 +-
 src/Lucene.Net.Core/Support/LinkedHashMap.cs    | 523 +++++++++++++++++++
 src/Lucene.Net.Tests/Lucene.Net.Tests.csproj    |   1 +
 .../core/Support/TestLinkedHashMap.cs           | 359 +++++++++++++
 5 files changed, 916 insertions(+), 17 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucenenet/blob/7723e26d/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 09915fb..5ce5f82 100644
--- a/src/Lucene.Net.Core/Lucene.Net.csproj
+++ b/src/Lucene.Net.Core/Lucene.Net.csproj
@@ -643,6 +643,7 @@
     <Compile Include="Support\IdentityWeakReference.cs" />
     <Compile Include="Support\IDictionaryExtensions.cs" />
     <Compile Include="Support\LimitedConcurrencyLevelTaskScheduler.cs" />
+    <Compile Include="Support\LinkedHashMap.cs" />
     <Compile Include="Support\ListExtensions.cs" />
     <Compile Include="Support\LongBuffer.cs" />
     <Compile Include="Support\LurchTable.cs" />

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/7723e26d/src/Lucene.Net.Core/Support/HashMap.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Support/HashMap.cs b/src/Lucene.Net.Core/Support/HashMap.cs
index 0fcf29a..ce3831d 100644
--- a/src/Lucene.Net.Core/Support/HashMap.cs
+++ b/src/Lucene.Net.Core/Support/HashMap.cs
@@ -22,6 +22,7 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
+using System.Text;
 
 namespace Lucene.Net.Support
 {
@@ -52,6 +53,20 @@ namespace Lucene.Net.Support
     /// </summary>
     /// <typeparam name="TKey">The type of keys in the dictionary</typeparam>
     /// <typeparam name="TValue">The type of values in the dictionary</typeparam>
+    /// <remarks>
+    /// <h2>Unordered Dictionaries</h2>
+    /// <list type="bullet">
+    ///     <item><see cref="Dictionary{TKey, TValue}"/> - use when order is not important and all keys are non-null.</item>
+    ///     <item><see cref="HashMap{TKey, TValue}"/> - use when order is not important and support for a null key is required.</item>
+    /// </list>
+    /// <h2>Ordered Dictionaries</h2>
+    /// <list type="bullet">
+    ///     <item><see cref="LinkedHashMap{TKey, TValue}"/> - use when you need to preserve entry insertion order. Keys are nullable.</item>
+    ///     <item><see cref="SortedDictionary{TKey, TValue}"/> - use when you need natural sort order. Keys must be unique.</item>
+    ///     <item><see cref="TreeDictionary{K, V}"/> - use when you need natural sort order. Keys may contain duplicates.</item>
+    ///     <item><see cref="LurchTable{TKey, TValue}"/> - use when you need to sort by most recent access or most recent update. Works well for LRU caching.</item>
+    /// </list>
+    /// </remarks>
     [Serializable]
     public class HashMap<TKey, TValue> : IDictionary<TKey, TValue>
     {
@@ -205,7 +220,7 @@ namespace Lucene.Net.Support
 
         #region Implementation of IEnumerable
 
-        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
+        public virtual IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
         {
             if (!_isValueType && _hasNullValue)
             {
@@ -226,38 +241,38 @@ namespace Lucene.Net.Support
 
         #region Implementation of ICollection<KeyValuePair<TKey,TValue>>
 
-        void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
+        public virtual void Add(KeyValuePair<TKey, TValue> item)
         {
             Add(item.Key, item.Value);
         }
 
-        public void Clear()
+        public virtual void Clear()
         {
             _hasNullValue = false;
             _nullValue = default(TValue);
             _dict.Clear();
         }
 
-        bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
+        public virtual bool Contains(KeyValuePair<TKey, TValue> item)
         {
             if (!_isValueType && _comparer.Equals(item.Key, default(TKey)))
             {
                 return _hasNullValue && EqualityComparer<TValue>.Default.Equals(item.Value, _nullValue);
             }
 
-            return ((ICollection<KeyValuePair<TKey, TValue>>)_dict).Contains(item);
+            return _dict.Contains(item);
         }
 
-        void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
+        public virtual void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
         {
-            ((ICollection<KeyValuePair<TKey, TValue>>)_dict).CopyTo(array, arrayIndex);
+            _dict.CopyTo(array, arrayIndex);
             if (!_isValueType && _hasNullValue)
             {
                 array[array.Length - 1] = new KeyValuePair<TKey, TValue>(default(TKey), _nullValue);
             }
         }
 
-        public bool Remove(KeyValuePair<TKey, TValue> item)
+        public virtual bool Remove(KeyValuePair<TKey, TValue> item)
         {
             if (!_isValueType && _comparer.Equals(item.Key, default(TKey)))
             {
@@ -269,15 +284,15 @@ namespace Lucene.Net.Support
                 return true;
             }
 
-            return ((ICollection<KeyValuePair<TKey, TValue>>)_dict).Remove(item);
+            return _dict.Remove(item);
         }
 
-        public int Count
+        public virtual int Count
         {
             get { return _dict.Count + (_hasNullValue ? 1 : 0); }
         }
 
-        public bool IsReadOnly
+        public virtual bool IsReadOnly
         {
             get { return false; }
         }
@@ -286,7 +301,7 @@ namespace Lucene.Net.Support
 
         #region Implementation of IDictionary<TKey,TValue>
 
-        public bool ContainsKey(TKey key)
+        public virtual bool ContainsKey(TKey key)
         {
             if (!_isValueType && _comparer.Equals(key, default(TKey)))
             {
@@ -313,7 +328,7 @@ namespace Lucene.Net.Support
             }
         }
 
-        public bool Remove(TKey key)
+        public virtual bool Remove(TKey key)
         {
             if (!_isValueType && _comparer.Equals(key, default(TKey)))
             {
@@ -327,7 +342,7 @@ namespace Lucene.Net.Support
             }
         }
 
-        public bool TryGetValue(TKey key, out TValue value)
+        public virtual bool TryGetValue(TKey key, out TValue value)
         {
             if (!_isValueType && _comparer.Equals(key, default(TKey)))
             {
@@ -346,7 +361,7 @@ namespace Lucene.Net.Support
             }
         }
 
-        public TValue this[TKey key]
+        public virtual TValue this[TKey key]
         {
             get
             {
@@ -363,7 +378,7 @@ namespace Lucene.Net.Support
             set { Add(key, value); }
         }
 
-        public ICollection<TKey> Keys
+        public virtual ICollection<TKey> Keys
         {
             get
             {
@@ -376,7 +391,7 @@ namespace Lucene.Net.Support
             }
         }
 
-        public ICollection<TValue> Values
+        public virtual ICollection<TValue> Values
         {
             get
             {

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/7723e26d/src/Lucene.Net.Core/Support/LinkedHashMap.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Core/Support/LinkedHashMap.cs b/src/Lucene.Net.Core/Support/LinkedHashMap.cs
new file mode 100644
index 0000000..5ae23ce
--- /dev/null
+++ b/src/Lucene.Net.Core/Support/LinkedHashMap.cs
@@ -0,0 +1,523 @@
+\ufeffusing System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Lucene.Net.Support
+{
+    /*
+    The MIT License (MIT)
+
+    Copyright (c) 2014 matarillo
+
+    Permission is hereby granted, free of charge, to any person obtaining a copy
+    of this software and associated documentation files (the "Software"), to deal
+    in the Software without restriction, including without limitation the rights
+    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+    copies of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included in
+    all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+    THE SOFTWARE.
+    */
+
+    // Source: http://qiita.com/matarillo/items/c09e0f3e5a61f84a51e2
+    // 2016-11-05 - Shad Storhaug
+    // Modified from its original form to be backed by a 
+    // HashMap rather than a Dictionary so it will support null keys.
+    // 2016-11-05 - Shad Storhaug
+    // Added KeyCollection and ValueCollection classes to prevent having to do
+    // an O(n) operation when calling the Keys or Values properties.
+
+    /// <summary>
+    /// LinkedHashMap is a specialized dictionary that preserves the entry order of elements.
+    /// Like a HashMap, there can be a <c>null</c> key, but it also guarantees that the enumeration
+    /// order of the elements are the same as insertion order, regardless of the number of add/remove/update
+    /// operations that are performed on it.
+    /// </summary>
+    /// <typeparam name="TKey">The type of keys in the dictionary</typeparam>
+    /// <typeparam name="TValue">The type of values in the dictionary</typeparam>
+    /// <remarks>
+    /// <h2>Unordered Dictionaries</h2>
+    /// <list type="bullet">
+    ///     <item><see cref="Dictionary{TKey, TValue}"/> - use when order is not important and all keys are non-null.</item>
+    ///     <item><see cref="HashMap{TKey, TValue}"/> - use when order is not important and support for a null key is required.</item>
+    /// </list>
+    /// <h2>Ordered Dictionaries</h2>
+    /// <list type="bullet">
+    ///     <item><see cref="LinkedHashMap{TKey, TValue}"/> - use when you need to preserve entry insertion order. Keys are nullable.</item>
+    ///     <item><see cref="SortedDictionary{TKey, TValue}"/> - use when you need natural sort order. Keys must be unique.</item>
+    ///     <item><see cref="TreeDictionary{K, V}"/> - use when you need natural sort order. Keys may contain duplicates.</item>
+    ///     <item><see cref="LurchTable{TKey, TValue}"/> - use when you need to sort by most recent access or most recent update. Works well for LRU caching.</item>
+    /// </list>
+    /// </remarks>
+    public class LinkedHashMap<TKey, TValue> : HashMap<TKey, TValue>, IDictionary<TKey, TValue>
+    {
+        private readonly HashMap<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> dict;
+        private readonly LinkedList<KeyValuePair<TKey, TValue>> list;
+
+        #region Constructors
+
+        public LinkedHashMap()
+        {
+            dict = new HashMap<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>();
+            list = new LinkedList<KeyValuePair<TKey, TValue>>();
+        }
+
+        public LinkedHashMap(IEqualityComparer<TKey> comparer)
+        {
+            dict = new HashMap<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>(comparer);
+            list = new LinkedList<KeyValuePair<TKey, TValue>>();
+        }
+
+        public LinkedHashMap(int capacity)
+        {
+            dict = new HashMap<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>(capacity);
+            list = new LinkedList<KeyValuePair<TKey, TValue>>();
+        }
+
+        public LinkedHashMap(int capacity, IEqualityComparer<TKey> comparer)
+        {
+            dict = new HashMap<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>(capacity, comparer);
+            list = new LinkedList<KeyValuePair<TKey, TValue>>();
+        }
+
+        public LinkedHashMap(IEnumerable<KeyValuePair<TKey, TValue>> source)
+        {
+            if (source == null)
+            {
+                throw new ArgumentNullException("source");
+            }
+            var countable = source as ICollection;
+            if (countable != null)
+            {
+                dict = new HashMap<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>(countable.Count);
+            }
+            else
+            {
+                dict = new HashMap<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>();
+            }
+            list = new LinkedList<KeyValuePair<TKey, TValue>>();
+            foreach (var pair in source)
+            {
+                this[pair.Key] = pair.Value;
+            }
+        }
+
+        public LinkedHashMap(IEnumerable<KeyValuePair<TKey, TValue>> source, IEqualityComparer<TKey> comparer)
+        {
+            if (source == null)
+            {
+                throw new ArgumentNullException("source");
+            }
+            var countable = source as ICollection;
+            if (countable != null)
+            {
+                dict = new HashMap<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>(countable.Count, comparer);
+            }
+            else
+            {
+                dict = new HashMap<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>(comparer);
+            }
+            list = new LinkedList<KeyValuePair<TKey, TValue>>();
+            foreach (var pair in source)
+            {
+                this[pair.Key] = pair.Value;
+            }
+        }
+
+        #endregion
+
+        #region IEnumerable implementation
+
+        public override IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
+        {
+            return list.GetEnumerator();
+        }
+
+        #endregion
+
+        #region IDictionary implementation
+
+        public override bool ContainsKey(TKey key)
+        {
+            return dict.ContainsKey(key);
+        }
+
+        public override void Add(TKey key, TValue value)
+        {
+            DoAdd(key, value);
+        }
+
+        private void DoAdd(TKey key, TValue value)
+        {
+            var pair = new KeyValuePair<TKey, TValue>(key, value);
+            var node = new LinkedListNode<KeyValuePair<TKey, TValue>>(pair);
+            dict.Add(key, node);
+            list.AddLast(node);
+        }
+
+        public override bool Remove(TKey key)
+        {
+            LinkedListNode<KeyValuePair<TKey, TValue>> n;
+            if (!dict.TryGetValue(key, out n))
+            {
+                return false;
+            }
+            DoRemove(n);
+            return true;
+        }
+
+        private void DoRemove(LinkedListNode<KeyValuePair<TKey, TValue>> node)
+        {
+            dict.Remove(node.Value.Key);
+            list.Remove(node);
+        }
+
+        public override bool TryGetValue(TKey key, out TValue value)
+        {
+            LinkedListNode<KeyValuePair<TKey, TValue>> n;
+            if (dict.TryGetValue(key, out n))
+            {
+                value = n.Value.Value;
+                return true;
+            }
+            value = default(TValue);
+            return false;
+        }
+
+        private bool TryGetNode(TKey key, TValue value, out LinkedListNode<KeyValuePair<TKey, TValue>> node)
+        {
+            LinkedListNode<KeyValuePair<TKey, TValue>> n;
+            if (dict.TryGetValue(key, out n) && EqualityComparer<TValue>.Default.Equals(value, n.Value.Value))
+            {
+                node = n;
+                return true;
+            }
+            node = null;
+            return false;
+        }
+
+        public override TValue this[TKey key]
+        {
+            get
+            {
+                var node = dict[key];
+                if (node == null)
+                    return default(TValue);
+
+                return node.Value.Value;
+            }
+            set
+            {
+                LinkedListNode<KeyValuePair<TKey, TValue>> n;
+                if (!dict.TryGetValue(key, out n))
+                {
+                    DoAdd(key, value);
+                    return;
+                }
+                DoSet(n, key, value);
+            }
+        }
+
+        private void DoSet(LinkedListNode<KeyValuePair<TKey, TValue>> node, TKey key, TValue value)
+        {
+            var pair = new KeyValuePair<TKey, TValue>(key, value);
+            var newNode = new LinkedListNode<KeyValuePair<TKey, TValue>>(pair);
+            dict[key] = newNode;
+            list.AddAfter(node, newNode);
+            list.Remove(node);
+        }
+
+        public override ICollection<TKey> Keys
+        {
+            get
+            {
+                return new KeyCollection(this);
+            }
+        }
+
+        public override ICollection<TValue> Values
+        {
+            get
+            {
+                return new ValueCollection(this);
+            }
+        }
+
+        #endregion
+
+        #region ICollection implementation
+
+        public override void Clear()
+        {
+            dict.Clear();
+            list.Clear();
+        }
+
+        public override int Count
+        {
+            get { return dict.Count; }
+        }
+
+        public override bool IsReadOnly
+        {
+            get { return false; }
+        }
+
+        public override void Add(KeyValuePair<TKey, TValue> item)
+        {
+            Add(item.Key, item.Value);
+        }
+
+        public override bool Contains(KeyValuePair<TKey, TValue> item)
+        {
+            LinkedListNode<KeyValuePair<TKey, TValue>> pair;
+            return TryGetNode(item.Key, item.Value, out pair);
+        }
+
+        public override void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
+        {
+            list.CopyTo(array, arrayIndex);
+        }
+
+        public override bool Remove(KeyValuePair<TKey, TValue> item)
+        {
+            LinkedListNode<KeyValuePair<TKey, TValue>> node;
+            if (!TryGetNode(item.Key, item.Value, out node))
+            {
+                return false;
+            }
+            DoRemove(node);
+            return true;
+        }
+
+        #endregion
+
+        #region KeyCollection
+
+        internal class KeyCollection : ICollection<TKey>
+        {
+            private readonly LinkedHashMap<TKey, TValue> outerInstance;
+
+            public KeyCollection(LinkedHashMap<TKey, TValue> outerInstance)
+            {
+                this.outerInstance = outerInstance;
+            }
+
+            public int Count
+            {
+                get
+                {
+                    return outerInstance.Count;
+                }
+            }
+
+            public bool IsReadOnly
+            {
+                get
+                {
+                    return false;
+                }
+            }
+
+            public void Add(TKey item)
+            {
+                throw new NotSupportedException();
+            }
+
+            public void Clear()
+            {
+                outerInstance.Clear();
+            }
+
+            public bool Contains(TKey item)
+            {
+                return outerInstance.ContainsKey(item);
+            }
+
+            public void CopyTo(TKey[] array, int arrayIndex)
+            {
+                throw new NotImplementedException("Implement this as needed");
+            }
+
+            public IEnumerator<TKey> GetEnumerator()
+            {
+                return new KeyEnumerator(outerInstance.list.GetEnumerator());
+            }
+
+            public bool Remove(TKey item)
+            {
+                return outerInstance.Remove(item);
+            }
+
+            IEnumerator IEnumerable.GetEnumerator()
+            {
+                return GetEnumerator();
+            }
+
+            #region KeyEnumerator
+
+            internal class KeyEnumerator : IEnumerator<TKey>
+            {
+                private readonly IEnumerator<KeyValuePair<TKey, TValue>> innerEnumerator;
+
+                public KeyEnumerator(IEnumerator<KeyValuePair<TKey, TValue>> innerEnumerator)
+                {
+                    this.innerEnumerator = innerEnumerator;
+                }
+
+                public TKey Current
+                {
+                    get
+                    {
+                        return innerEnumerator.Current.Key;
+                    }
+                }
+
+                object IEnumerator.Current
+                {
+                    get
+                    {
+                        return Current;
+                    }
+                }
+
+                public void Dispose()
+                {
+                    innerEnumerator.Dispose();
+                }
+
+                public bool MoveNext()
+                {
+                    return innerEnumerator.MoveNext();
+                }
+
+                public void Reset()
+                {
+                    innerEnumerator.Reset();
+                }
+            }
+
+            #endregion
+        }
+
+        #endregion
+
+        #region ValueCollection
+
+        internal class ValueCollection : ICollection<TValue>
+        {
+            private readonly LinkedHashMap<TKey, TValue> outerInstance;
+
+            public ValueCollection(LinkedHashMap<TKey, TValue> outerInstance)
+            {
+                this.outerInstance = outerInstance;
+            }
+
+            public int Count
+            {
+                get
+                {
+                    return outerInstance.Count;
+                }
+            }
+
+            public bool IsReadOnly
+            {
+                get
+                {
+                    return false;
+                }
+            }
+
+            public void Add(TValue item)
+            {
+                throw new NotSupportedException();
+            }
+
+            public void Clear()
+            {
+                outerInstance.Clear();
+            }
+
+            public bool Contains(TValue item)
+            {
+                return outerInstance.ContainsValue(item);
+            }
+
+            public void CopyTo(TValue[] array, int arrayIndex)
+            {
+                throw new NotImplementedException("Implement this as needed");
+            }
+
+            public IEnumerator<TValue> GetEnumerator()
+            {
+                return new ValueEnumerator(outerInstance.list.GetEnumerator());
+            }
+
+            public bool Remove(TValue item)
+            {
+                throw new NotSupportedException();
+            }
+
+            IEnumerator IEnumerable.GetEnumerator()
+            {
+                return GetEnumerator();
+            }
+
+
+            internal class ValueEnumerator : IEnumerator<TValue>
+            {
+                private readonly IEnumerator<KeyValuePair<TKey, TValue>> innerEnumerator;
+
+                public ValueEnumerator(IEnumerator<KeyValuePair<TKey, TValue>> innerEnumerator)
+                {
+                    this.innerEnumerator = innerEnumerator;
+                }
+
+                public TValue Current
+                {
+                    get
+                    {
+                        return innerEnumerator.Current.Value;
+                    }
+                }
+
+                object IEnumerator.Current
+                {
+                    get
+                    {
+                        return Current;
+                    }
+                }
+
+                public void Dispose()
+                {
+                    innerEnumerator.Dispose();
+                }
+
+                public bool MoveNext()
+                {
+                    return innerEnumerator.MoveNext();
+                }
+
+                public void Reset()
+                {
+                    innerEnumerator.Reset();
+                }
+            }
+        }
+
+        
+
+        #endregion
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/7723e26d/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 0b0ad11..9e6377e 100644
--- a/src/Lucene.Net.Tests/Lucene.Net.Tests.csproj
+++ b/src/Lucene.Net.Tests/Lucene.Net.Tests.csproj
@@ -484,6 +484,7 @@
     <Compile Include="core\Support\C5\WeakViewList.cs" />
     <Compile Include="core\Support\C5\Wrappers.cs" />
     <Compile Include="core\Support\TestHashMap.cs" />
+    <Compile Include="core\Support\TestLinkedHashMap.cs" />
     <Compile Include="core\Support\TestLongBuffer.cs" />
     <Compile Include="core\Support\TestByteBuffer.cs" />
     <Compile Include="core\Support\TestLurchTable.cs" />

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/7723e26d/src/Lucene.Net.Tests/core/Support/TestLinkedHashMap.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests/core/Support/TestLinkedHashMap.cs b/src/Lucene.Net.Tests/core/Support/TestLinkedHashMap.cs
new file mode 100644
index 0000000..73875e2
--- /dev/null
+++ b/src/Lucene.Net.Tests/core/Support/TestLinkedHashMap.cs
@@ -0,0 +1,359 @@
+\ufeffusing Lucene.Net.Attributes;
+using NUnit.Framework;
+using System.Collections.Generic;
+
+namespace Lucene.Net.Support
+{
+    /*
+     * 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 TestLinkedHashMap : TestHashMap
+    {
+        protected override HashMap<TKey, TValue> GetNewHashMap<TKey, TValue>()
+        {
+            return new LinkedHashMap<TKey, TValue>();
+        }
+
+        private IDictionary<FakeKey<int>, string> GetDefaultHashMap2()
+        {
+            var dict = GetNewHashMap<FakeKey<int>, string>();
+
+            dict[new FakeKey<int>(2)] = "MyString";
+            dict[new FakeKey<int>(0)] = "OtherString";
+            dict[new FakeKey<int>(5)] = "NumberFive";
+            dict[new FakeKey<int>(int.MaxValue)] = "Maximum";
+            dict[null] = "NullValue";
+            dict[new FakeKey<int>(int.MinValue)] = "Minimum";
+
+            return dict;
+        }
+
+        internal class FakeKey<T>
+        {
+            private readonly T key;
+
+            public FakeKey(T key)
+            {
+                this.key = key;
+            }
+
+            public override bool Equals(object obj)
+            {
+                // NOTE: This takes into consideration that value
+                // types cannot be null.
+                if (key == null && obj == null)
+                {
+                    return true;
+                }
+
+                if (!(obj is FakeKey<T>))
+                {
+                    return false;
+                }
+
+                return key.Equals(((FakeKey<T>)obj).key);
+            }
+
+            public override int GetHashCode()
+            {
+                // NOTE: This takes into consideration that value
+                // types cannot be null.
+                if (key == null)
+                {
+                    return 0; // Emulates Objects.hashcode(object) in Java
+                }
+
+                return key.GetHashCode();
+            }
+
+            public override string ToString()
+            {
+                // NOTE: This takes into consideration that value
+                // types cannot be null.
+                if (key == null)
+                {
+                    return "null"; // Emulates String.valueOf(object) in Java
+                }
+
+                return key.ToString();
+            }
+        }
+
+        [Test, LuceneNetSpecific]
+        public void TestInsertionOrderNullFirst()
+        {
+            var dict = GetNewHashMap<FakeKey<int>, string>();
+
+            dict[null] = "NullValue";
+            dict[new FakeKey<int>(2)] = "MyString";
+            dict[new FakeKey<int>(0)] = "OtherString";
+            dict[new FakeKey<int>(5)] = "NumberFive";
+            dict[new FakeKey<int>(int.MaxValue)] = "Maximum";
+
+            var expectedOrder = new List<KeyValuePair<FakeKey<int>, string>>
+            {
+                new KeyValuePair<FakeKey<int>, string>(null, "NullValue"),
+                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(2), "MyString"),
+                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(0), "OtherString"),
+                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(5), "NumberFive"),
+                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(int.MaxValue), "Maximum"),
+            };
+
+            AssertEnumerationOrder(expectedOrder, dict);
+        }
+
+        [Test, LuceneNetSpecific]
+        public void TestInsertionOrderNullInMiddle()
+        {
+            var dict = GetNewHashMap<FakeKey<int>, string>();
+
+            dict[new FakeKey<int>(2)] = "MyString";
+            dict[new FakeKey<int>(0)] = "OtherString";
+            dict[null] = "NullValue";
+            dict[new FakeKey<int>(5)] = "NumberFive";
+            dict[new FakeKey<int>(int.MaxValue)] = "Maximum";
+
+            var expectedOrder = new List<KeyValuePair<FakeKey<int>, string>>
+            {
+                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(2), "MyString"),
+                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(0), "OtherString"),
+                new KeyValuePair<FakeKey<int>, string>(null, "NullValue"),
+                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(5), "NumberFive"),
+                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(int.MaxValue), "Maximum"),
+            };
+
+            AssertEnumerationOrder(expectedOrder, dict);
+        }
+
+        [Test, LuceneNetSpecific]
+        public void TestInsertionOrderNullLast()
+        {
+            var dict = GetNewHashMap<FakeKey<int>, string>();
+
+            dict[new FakeKey<int>(2)] = "MyString";
+            dict[new FakeKey<int>(0)] = "OtherString";
+            dict[new FakeKey<int>(5)] = "NumberFive";
+            dict[new FakeKey<int>(int.MaxValue)] = "Maximum";
+            dict[null] = "NullValue";
+
+            var expectedOrder = new List<KeyValuePair<FakeKey<int>, string>>
+            {
+                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(2), "MyString"),
+                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(0), "OtherString"),
+                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(5), "NumberFive"),
+                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(int.MaxValue), "Maximum"),
+                new KeyValuePair<FakeKey<int>, string>(null, "NullValue"),
+            };
+
+            AssertEnumerationOrder(expectedOrder, dict);
+        }
+
+        [Test, LuceneNetSpecific]
+        public void TestInsertionOrderNullAfterRemovingElements()
+        {
+            var dict = GetDefaultHashMap2();
+
+            // Remove elements before the null value to make sure
+            // we track the proper position of the null
+            dict.Remove(new FakeKey<int>(5));
+            dict.Remove(new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(0), "OtherString"));
+
+            var expectedOrder = new List<KeyValuePair<FakeKey<int>, string>>
+            {
+                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(2), "MyString"),
+                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(int.MaxValue), "Maximum"),
+                new KeyValuePair<FakeKey<int>, string>(null, "NullValue"),
+                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(int.MinValue), "Minimum"),
+            };
+
+            AssertEnumerationOrder(expectedOrder, dict);
+        }
+
+
+        [Test, LuceneNetSpecific]
+        public void TestInsertionOrderNullAfterRefillingBuckets()
+        {
+            var dict = GetDefaultHashMap2();
+
+            // Remove elements before the null value to make sure
+            // we track the proper position of the null
+            dict.Remove(new FakeKey<int>(5));
+            dict.Remove(new FakeKey<int>(0));
+
+            // This has been verified that it puts the reused key before "new FakeKey<int>(int.MaxValue)"
+            // in a standard Dictionary (putting it out of insertion order)
+            dict.Add(new FakeKey<int>(5), "Testing");
+
+            var expectedOrder = new List<KeyValuePair<FakeKey<int>, string>>
+            {
+                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(2), "MyString"),
+                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(int.MaxValue), "Maximum"),
+                new KeyValuePair<FakeKey<int>, string>(null, "NullValue"),
+                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(int.MinValue), "Minimum"),
+                new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(5), "Testing"),
+            };
+
+            AssertEnumerationOrder(expectedOrder, dict);
+        }
+
+        private void AssertEnumerationOrder<TKey, TValue>(IList<KeyValuePair<TKey, TValue>> expectedOrder, IDictionary<TKey, TValue> actualOrder)
+        {
+            // check element order
+            int expectedCount = expectedOrder.Count;
+            var elementEnumerator = actualOrder.GetEnumerator();
+            for (int i = 0; i < expectedCount; i++)
+            {
+                Assert.IsTrue(elementEnumerator.MoveNext());
+                Assert.AreEqual(expectedOrder[i].Key, elementEnumerator.Current.Key);
+                Assert.AreEqual(expectedOrder[i].Value, elementEnumerator.Current.Value);
+            }
+
+            Assert.IsFalse(elementEnumerator.MoveNext());
+            Assert.IsFalse(elementEnumerator.MoveNext());
+
+            // check key order
+            var keyEnumerator = actualOrder.Keys.GetEnumerator();
+            for (int i = 0; i < expectedCount; i++)
+            {
+                Assert.IsTrue(keyEnumerator.MoveNext());
+                Assert.AreEqual(expectedOrder[i].Key, keyEnumerator.Current);
+            }
+
+            Assert.IsFalse(keyEnumerator.MoveNext());
+            Assert.IsFalse(keyEnumerator.MoveNext());
+
+            // check value order
+            var valueEnumerator = actualOrder.Values.GetEnumerator();
+            for (int i = 0; i < expectedCount; i++)
+            {
+                Assert.IsTrue(valueEnumerator.MoveNext());
+                Assert.AreEqual(expectedOrder[i].Value, valueEnumerator.Current);
+            }
+
+            Assert.IsFalse(valueEnumerator.MoveNext());
+            Assert.IsFalse(valueEnumerator.MoveNext());
+        }
+
+
+        [Test, LuceneNetSpecific]
+        public void TestCopyTo()
+        {
+            var dict = GetDefaultHashMap2();
+
+            KeyValuePair<FakeKey<int>, string>[] elements = new KeyValuePair<FakeKey<int>, string>[dict.Count];
+
+            dict.CopyTo(elements, 0);
+
+            // element insertion order
+            var enumerator = elements.GetEnumerator();
+
+            Assert.IsTrue(enumerator.MoveNext());
+            Assert.AreEqual(new FakeKey<int>(2), ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
+            Assert.AreEqual("MyString", ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);
+
+            Assert.IsTrue(enumerator.MoveNext());
+            Assert.AreEqual(new FakeKey<int>(0), ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
+            Assert.AreEqual("OtherString", ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);
+
+            Assert.IsTrue(enumerator.MoveNext());
+            Assert.AreEqual(new FakeKey<int>(5), ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
+            Assert.AreEqual("NumberFive", ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);
+
+            Assert.IsTrue(enumerator.MoveNext());
+            Assert.AreEqual(new FakeKey<int>(int.MaxValue), ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
+            Assert.AreEqual("Maximum", ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);
+
+            Assert.IsTrue(enumerator.MoveNext());
+            Assert.AreEqual(null, ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
+            Assert.AreEqual("NullValue", ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);
+
+            Assert.IsTrue(enumerator.MoveNext());
+            Assert.AreEqual(new FakeKey<int>(int.MinValue), ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
+            Assert.AreEqual("Minimum", ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);
+
+            Assert.IsFalse(enumerator.MoveNext());
+            Assert.IsFalse(enumerator.MoveNext());
+        }
+
+        [Test, LuceneNetSpecific]
+        public void TestCopyToWithOffset()
+        {
+            int offset = 5;
+            var dict = GetDefaultHashMap2();
+
+            KeyValuePair<FakeKey<int>, string>[] elements = new KeyValuePair<FakeKey<int>, string>[dict.Count + offset];
+
+            dict.CopyTo(elements, offset);
+
+            // element insertion order
+            var enumerator = elements.GetEnumerator();
+
+            for (int i = 0; i < offset; i++)
+            {
+                Assert.IsTrue(enumerator.MoveNext());
+                Assert.AreEqual(null, ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
+                Assert.AreEqual(null, ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);
+            }
+
+            Assert.IsTrue(enumerator.MoveNext());
+            Assert.AreEqual(new FakeKey<int>(2), ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
+            Assert.AreEqual("MyString", ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);
+
+            Assert.IsTrue(enumerator.MoveNext());
+            Assert.AreEqual(new FakeKey<int>(0), ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
+            Assert.AreEqual("OtherString", ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);
+
+            Assert.IsTrue(enumerator.MoveNext());
+            Assert.AreEqual(new FakeKey<int>(5), ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
+            Assert.AreEqual("NumberFive", ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);
+
+            Assert.IsTrue(enumerator.MoveNext());
+            Assert.AreEqual(new FakeKey<int>(int.MaxValue), ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
+            Assert.AreEqual("Maximum", ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);
+
+            Assert.IsTrue(enumerator.MoveNext());
+            Assert.AreEqual(null, ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
+            Assert.AreEqual("NullValue", ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);
+
+            Assert.IsTrue(enumerator.MoveNext());
+            Assert.AreEqual(new FakeKey<int>(int.MinValue), ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Key);
+            Assert.AreEqual("Minimum", ((KeyValuePair<FakeKey<int>, string>)enumerator.Current).Value);
+
+            Assert.IsFalse(enumerator.MoveNext());
+            Assert.IsFalse(enumerator.MoveNext());
+        }
+
+        [Test, LuceneNetSpecific]
+        public void TestContainsKeyValuePair()
+        {
+            var dict = GetDefaultHashMap2();
+
+            var realTarget = new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(5), "NumberFive");
+            Assert.IsTrue(dict.Contains(realTarget));
+
+            var nullTarget = new KeyValuePair<FakeKey<int>, string>(null, "NullValue");
+            Assert.IsTrue(dict.Contains(nullTarget));
+
+            var invalidTarget = new KeyValuePair<FakeKey<int>, string>(new FakeKey<int>(543), "NullValue");
+            Assert.IsFalse(dict.Contains(invalidTarget));
+
+            dict.Remove(nullTarget);
+            Assert.IsFalse(dict.Contains(nullTarget));
+        }
+    }
+}