1771 lines · 67.1 KB · C#
RawDownload
//******************************************************************************************************//  FileBackedLookupTableBase.cs - Gbtc////  Copyright © 2014, Grid Protection Alliance.  All Rights Reserved.////  Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See//  the NOTICE file distributed with this work for additional information regarding copyright ownership.//  The GPA licenses this file to you under the MIT License (MIT), 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.opensource.org/licenses/MIT////  Unless agreed to in writing, the subject software distributed under the License is distributed on an//  "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the//  License for the specific language governing permissions and limitations.////  Code Modification History://  ----------------------------------------------------------------------------------------------------//  12/02/2014 - Stephen C. Wills//       Generated original version of source code.////****************************************************************************************************** using System;using System.Collections;using System.Collections.Generic;using System.IO;using System.Linq;using System.Linq.Expressions;using System.Reflection;using System.Runtime.Serialization.Formatters.Binary;using GSF.IO;using GSF.IO.Checksums; namespace GSF.Collections{    internal enum LookupTableType    {        /// <summary>        /// Dictionary with keys and values in the item nodes.        /// </summary>        Dictionary,         /// <summary>        /// HashSet with lookup node markers for set operations.        /// </summary>        HashSet    }     internal class FileBackedLookupTable<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>, IDisposable    {        #region [ Members ]         // Nested Types         protected class HeaderNode        {            public const int SignatureSize = 16;            public const int FixedSize = SignatureSize + 4 * sizeof(long);             public HeaderNode(LookupTableType type)            {                if (type == LookupTableType.Dictionary)                    Signature = Guid.Parse(DictionarySignature).ToRfcBytes();                else                    Signature = Guid.Parse(HashSetSignature).ToRfcBytes();            }             public byte[] Signature;            public long Count;            public long Capacity;            public long ItemSectionPointer;            public long EndOfFilePointer;        }         protected class JournalNode        {            public const int FixedSize = sizeof(int) + 3 * sizeof(long) + sizeof(int);             public const int None = 0;            public const int Set = 1;            public const int Delete = 2;            public const int GrowLookupSection = 3;            public const int RebuildLookupTable = 4;            public const int WriteItemNodePointers = 5;            public const int Truncate = 6;            public const int Clear = 7;             public int Operation;            public long LookupPointer;            public long ItemPointer;            public long Sync;            public int Checksum;        }         protected class LookupNode        {            public long ItemPointer;            public int Marker;        }         protected class ItemNode        {            // Size of only the fixed size portion of            // the item node (not including key and value)            public const int FixedSize = 2 * sizeof(long) + sizeof(int);             public long LookupPointer;            public long NextItemPointer;            public int HashCode;            public TKey Key;            public TValue Value;        }         private class KeysEnumerable : IEnumerable<TKey>        {            private Func<IEnumerator<TKey>> m_getEnumeratorFunc;             public KeysEnumerable(Func<IEnumerator<TKey>> getEnumeratorFunc)            {                m_getEnumeratorFunc = getEnumeratorFunc;            }             public IEnumerator<TKey> GetEnumerator()            {                return m_getEnumeratorFunc();            }             IEnumerator IEnumerable.GetEnumerator()            {                return GetEnumerator();            }        }         // Constants        public const string DictionarySignature = "3165E4F9-203B-4741-A186-EA34659A94B7";        public const string HashSetSignature = "6527713F-78AE-43DA-8E37-718AFED99927";        private const double MaximumLoadFactor = 0.7D;        private const int MaximumChainedEmptyNodes = 3;         // Fields        private HeaderNode m_headerNode;        private JournalNode m_journalNode;         private CachedFileStream m_fileStream;        private BinaryWriter m_fileWriter;        private BinaryReader m_fileReader;         private string m_filePath;        private IEqualityComparer<TKey> m_keyComparer;        private int m_fragmentationCount;         private LookupTableType m_lookupTableType;         #endregion         #region [ Constructors ]         /// <summary>        /// Creates a new instance of the <see cref="FileBackedLookupTable{TKey, TValue}"/> class.        /// </summary>        /// <param name="lookupTableType">Type of the lookup table used to tweak the file format.</param>        /// <exception cref="InvalidOperationException">Either <typeparamref name="TKey"/> or <typeparamref name="TValue"/> cannot be serialized.</exception>        public FileBackedLookupTable(LookupTableType lookupTableType)            : this(lookupTableType, EqualityComparer<TKey>.Default)        {        }         /// <summary>        /// Creates a new instance of the <see cref="FileBackedLookupTable{TKey, TValue}"/> class.        /// </summary>        /// <param name="lookupTableType">Type of the lookup table used to tweak the file format.</param>        /// <param name="filePath">The path to the file used to store the lookup table.</param>        /// <exception cref="ArgumentException"><paramref name="filePath"/> is a zero-length string, contains only white space, or contains one or more invalid characters as defined by <see cref="Path.GetInvalidPathChars"/>.</exception>        /// <exception cref="ArgumentNullException"><paramref name="filePath"/> is null.</exception>        /// <exception cref="InvalidOperationException">Either <typeparamref name="TKey"/> or <typeparamref name="TValue"/> cannot be serialized.</exception>        public FileBackedLookupTable(LookupTableType lookupTableType, string filePath)            : this(lookupTableType, filePath, EqualityComparer<TKey>.Default)        {        }         /// <summary>        /// Creates a new instance of the <see cref="FileBackedLookupTable{TKey, TValue}"/> class.        /// </summary>        /// <param name="lookupTableType">Type of the lookup table used to tweak the file format.</param>        /// <param name="keyComparer">The equality comparer used to compare keys in the lookup table.</param>        /// <exception cref="InvalidOperationException">Either <typeparamref name="TKey"/> or <typeparamref name="TValue"/> cannot be serialized.</exception>        public FileBackedLookupTable(LookupTableType lookupTableType, IEqualityComparer<TKey> keyComparer)            : this(lookupTableType, Path.GetTempFileName(), keyComparer)        {        }         /// <summary>        /// Creates a new instance of the <see cref="FileBackedLookupTable{TKey, TValue}"/> class.        /// </summary>        /// <param name="lookupTableType">Type of the lookup table used to tweak the file format.</param>        /// <param name="filePath">The path to the file used to store the lookup table.</param>        /// <param name="keyComparer">The equality comparer used to compare keys in the lookup table.</param>        /// <exception cref="ArgumentException"><paramref name="filePath"/> is a zero-length string, contains only white space, or contains one or more invalid characters as defined by <see cref="Path.GetInvalidPathChars"/>.</exception>        /// <exception cref="ArgumentNullException"><paramref name="filePath"/> is null.</exception>        /// <exception cref="InvalidOperationException">Either <typeparamref name="TKey"/> or <typeparamref name="TValue"/> cannot be serialized.</exception>        public FileBackedLookupTable(LookupTableType lookupTableType, string filePath, IEqualityComparer<TKey> keyComparer)        {            if ((object)WriteKeyAction == null || (object)ReadKeyFunc == null)                throw new InvalidOperationException("Unable to create FileBackedLookupTable with keys that are not serializable");             if (lookupTableType == LookupTableType.Dictionary && ((object)WriteValueAction == null || (object)ReadValueFunc == null))                throw new InvalidOperationException("Unable to create FileBackedLookupTable with values that are not serializable");             if ((object)filePath == null)                throw new ArgumentNullException("filePath");             if (string.IsNullOrWhiteSpace(filePath))                throw new ArgumentException("Path is zero-length or contains only whitespace", "filePath");             m_lookupTableType = lookupTableType;             FilePath = filePath;             if ((object)keyComparer != null)                m_keyComparer = keyComparer;            else                m_keyComparer = EqualityComparer<TKey>.Default;        }         #endregion         #region [ Properties ]         /// <summary>        /// Gets or sets the path to the file backing this lookup table.        /// </summary>        /// <exception cref="ArgumentException">FilePath contains one or more invalid characters as defined by <see cref="Path.GetInvalidPathChars"/>.</exception>        /// <remarks>        /// Changes to this property will cause the file to close if the file is already opened.        /// Data will not be automatically written from the old file to the new file.        /// </remarks>        public string FilePath        {            get            {                return m_filePath;            }            set            {                char[] invalidPathChars;                 if (m_filePath != value)                {                    if (!string.IsNullOrWhiteSpace(value))                    {                        invalidPathChars = Path.GetInvalidPathChars();                         if (value.Any(invalidPathChars.Contains))                            throw new ArgumentException("Path contains one or more invalid characters", "value");                    }                     Close();                    m_filePath = value;                }            }        }         /// <summary>        /// Gets the number of elements contained in the <see cref="FileBackedLookupTable{TKey, TValue}"/>.        /// </summary>        /// <returns>        /// The number of elements contained in the <see cref="FileBackedLookupTable{TKey, TValue}"/>.        /// </returns>        public int Count        {            get            {                if ((object)m_fileStream == null)                    Open();                 return (int)m_headerNode.Count;            }        }         /// <summary>        /// Gets a value indicating whether the <see cref="FileBackedLookupTable{TKey, TValue}"/> is read-only.        /// </summary>        /// <returns>        /// true if the <see cref="FileBackedLookupTable{TKey, TValue}"/> is read-only; otherwise, false.        /// </returns>        public bool IsReadOnly        {            get            {                if ((object)m_fileStream == null)                    Open();                 return (object)m_fileWriter == null;            }        }         /// <summary>        /// Gets or sets the signature of the file backing the lookup table.        /// </summary>        /// <exception cref="ArgumentNullException">Attempt is made to set Signature to a null value.</exception>        /// <exception cref="ArgumentException">Attempt is made to set Signature to a value larger than the maximum allowed size.</exception>        /// <exception cref="NotSupportedException">Attempt is made to modify Signature of a read-only lookup table.</exception>        public byte[] Signature        {            get            {                byte[] signature;                 if ((object)m_fileStream == null)                    Open();                 signature = new byte[HeaderNode.SignatureSize];                Buffer.BlockCopy(m_headerNode.Signature, 0, signature, 0, HeaderNode.SignatureSize);                return signature;            }            set            {                if ((object)value == null)                    throw new ArgumentNullException("value");                 if (value.Length > HeaderNode.SignatureSize)                    throw new ArgumentException("Attempt was made to set signature to a value larger than the maximum signature size of " + HeaderNode.SignatureSize);                 if (IsReadOnly)                    throw new NotSupportedException("Unable to modify read-only lookup table");                 Buffer.BlockCopy(value, 0, m_headerNode.Signature, 0, value.Length);                Array.Clear(m_headerNode.Signature, value.Length, HeaderNode.SignatureSize - value.Length);                Write(m_headerNode);            }        }         /// <summary>        /// Gets or sets the element with the specified key.        /// </summary>        /// <returns>        /// The element with the specified key.        /// </returns>        /// <param name="key">The key of the element to get or set.</param>        /// <exception cref="ArgumentNullException"><paramref name="key"/> is null.</exception>        /// <exception cref="KeyNotFoundException">The property is retrieved and <paramref name="key"/> is not found.</exception>        /// <exception cref="NotSupportedException">The property is set and the <see cref="FileBackedLookupTable{TKey, TValue}"/> is read-only.</exception>        public TValue this[TKey key]        {            get            {                TValue value;                 if ((object)key == null)                    throw new ArgumentNullException("key");                 if (!TryGetValue(key, out value))                    throw new KeyNotFoundException("Item with the given key was not found in the lookup table");                 return value;            }            set            {                long lookupPointer;                long itemPointer;                ItemNode itemNode;                 long count;                 if ((object)key == null)                    throw new ArgumentNullException("key");                 if (IsReadOnly)                    throw new NotSupportedException("Unable to modify read-only lookup table");                 Find(key, out lookupPointer, out itemPointer);                 if (itemPointer >= m_headerNode.ItemSectionPointer)                    count = m_headerNode.Count;                else                    count = m_headerNode.Count + 1;                 itemNode = new ItemNode();                itemNode.LookupPointer = lookupPointer;                itemNode.HashCode = m_keyComparer.GetHashCode(key);                itemNode.Key = key;                itemNode.Value = value;                 m_fileStream.Seek(m_headerNode.EndOfFilePointer, SeekOrigin.Begin);                Write(itemNode);                Set(lookupPointer, m_headerNode.EndOfFilePointer, count);                 if (itemPointer >= m_headerNode.ItemSectionPointer)                    m_fragmentationCount++;            }        }         /// <summary>        /// Gets or sets the size of the cache used        /// to store data from the file in memory.        /// </summary>        public long CacheSize        {            get            {                if ((object)m_fileStream == null)                    Open();                 // ReSharper disable once PossibleNullReferenceException                return m_fileStream.CacheSize;            }            set            {                if ((object)m_fileStream == null)                    Open();                 // ReSharper disable once PossibleNullReferenceException                m_fileStream.CacheSize = value;            }        }         /// <summary>        /// Gets the number of operations that fragment the        /// lookup table that have occurred since the last        /// time the lookup table was compacted.        /// </summary>        /// <remarks>        /// This value is not stored in the file and may therefore        /// be inaccurate if the lookup table has not been compacted        /// since the last time it was opened.        /// </remarks>        public int FragmentationCount        {            get            {                return m_fragmentationCount;            }        }         private int LookupNodeSize        {            get            {                const int FixedSize = sizeof(long);                int variableSize = (m_lookupTableType == LookupTableType.HashSet) ? sizeof(int) : 0;                return FixedSize + variableSize;            }        }         #endregion         #region [ Methods ]         /// <summary>        /// Opens the file backing this lookup table.        /// </summary>        /// <exception cref="InvalidOperationException">File is already open.</exception>        public void Open()        {            string directory;             if ((object)m_fileStream != null)                throw new InvalidOperationException("File is already open");             m_headerNode = new HeaderNode(m_lookupTableType);            m_journalNode = new JournalNode();             if (string.IsNullOrWhiteSpace(m_filePath))                m_filePath = Path.GetTempFileName();             try            {                directory = Path.GetDirectoryName(m_filePath);                 // Attempt to create the directory if it doesn't already exist                if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))                    Directory.CreateDirectory(directory);                 // Open the file in read/write mode                m_fileStream = new CachedFileStream(m_filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);                m_fileWriter = new BinaryWriter(m_fileStream);                m_fileReader = new BinaryReader(m_fileStream);                 if (m_fileStream.Length > HeaderNode.FixedSize + JournalNode.FixedSize)                {                    // Read the header node and the journal                    // node from the start of the file                    Read(m_headerNode);                    Read(m_journalNode);                     // If the journal node has an indicates that an operation was interrupted                    // the last time this file was open, replay that operation to return the                    // file to a consistent state                    switch (m_journalNode.Operation)                    {                        case JournalNode.Set:                            Set(m_journalNode.LookupPointer, m_journalNode.ItemPointer, m_journalNode.Sync);                            break;                         case JournalNode.Delete:                            Delete(m_journalNode.LookupPointer, m_journalNode.Sync);                            break;                         case JournalNode.GrowLookupSection:

Lines 1–500 of 1771.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t