From e3e31cd9f22eb95bcc0a2f5af0286ce19154ad36 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 19 Aug 2025 07:27:39 -0400 Subject: [PATCH] Separate files --- .../Wrappers/WrapperBase.cs | 373 ----------------- .../Wrappers/WrapperBaseT.cs | 377 ++++++++++++++++++ 2 files changed, 377 insertions(+), 373 deletions(-) create mode 100644 SabreTools.Serialization/Wrappers/WrapperBaseT.cs diff --git a/SabreTools.Serialization/Wrappers/WrapperBase.cs b/SabreTools.Serialization/Wrappers/WrapperBase.cs index 14dec2c1..1dc94119 100644 --- a/SabreTools.Serialization/Wrappers/WrapperBase.cs +++ b/SabreTools.Serialization/Wrappers/WrapperBase.cs @@ -1,8 +1,3 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using SabreTools.IO.Extensions; using SabreTools.Serialization.Interfaces; namespace SabreTools.Serialization.Wrappers @@ -32,372 +27,4 @@ namespace SabreTools.Serialization.Wrappers #endregion } - - public abstract class WrapperBase : WrapperBase, IWrapper - { - #region Properties - - /// - public T GetModel() => Model; - - /// - /// Internal model - /// - public T Model { get; private set; } - - /// - /// Length of the underlying data - /// - public long Length - { - get - { - return _dataSource switch - { - DataSource.ByteArray => _byteArrayData!.Length - _byteArrayOffset, - DataSource.Stream => _streamData!.Length, - - // Everything else is invalid - _ => -1, - }; - } - } - - #endregion - - #region Instance Variables - - /// - /// Source of the original data - /// - protected DataSource _dataSource = DataSource.UNKNOWN; - - /// - /// Lock object for reading from the source - /// - private readonly object _streamDataLock = new(); - - /// - /// Source byte array data - /// - /// This is only populated if is - protected byte[]? _byteArrayData = null; - - /// - /// Source byte array data offset - /// - /// This is only populated if is - protected int _byteArrayOffset = -1; - - /// - /// Source Stream data - /// - /// This is only populated if is - protected Stream? _streamData = null; - -#if NETCOREAPP - /// - /// JSON serializer options for output printing - /// - protected System.Text.Json.JsonSerializerOptions _jsonSerializerOptions - { - get - { -#if NETCOREAPP3_1 - var serializer = new System.Text.Json.JsonSerializerOptions { WriteIndented = true }; -#else - var serializer = new System.Text.Json.JsonSerializerOptions { IncludeFields = true, WriteIndented = true }; -#endif - serializer.Converters.Add(new ConcreteAbstractSerializer()); - serializer.Converters.Add(new ConcreteInterfaceSerializer()); - serializer.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter()); - return serializer; - } - } -#endif - - #endregion - - #region Constructors - - /// - /// Construct a new instance of the wrapper from a byte array - /// - protected WrapperBase(T? model, byte[]? data, int offset) - { - if (model == null) - throw new ArgumentNullException(nameof(model)); - if (data == null) - throw new ArgumentNullException(nameof(data)); - if (offset < 0 || offset >= data.Length) - throw new ArgumentOutOfRangeException(nameof(offset)); - - Model = model; - _dataSource = DataSource.ByteArray; - _byteArrayData = data; - _byteArrayOffset = offset; - } - - /// - /// Construct a new instance of the wrapper from a Stream - /// - protected WrapperBase(T? model, Stream? data) - { - if (model == null) - throw new ArgumentNullException(nameof(model)); - if (data == null) - throw new ArgumentNullException(nameof(data)); - if (!data.CanSeek || !data.CanRead) - throw new ArgumentOutOfRangeException(nameof(data)); - - Model = model; - _dataSource = DataSource.Stream; - _streamData = data; - } - - #endregion - - #region Data - - /// - /// Validate the backing data source - /// - /// True if the data source is valid, false otherwise - public bool DataSourceIsValid() - { - return _dataSource switch - { - // Byte array data requires both a valid array and offset - DataSource.ByteArray => _byteArrayData != null && _byteArrayOffset >= 0, - - // Stream data requires both a valid stream - DataSource.Stream => _streamData != null && _streamData.CanRead && _streamData.CanSeek, - - // Everything else is invalid - _ => false, - }; - } - - /// - /// Check if a data segment is valid in the data source - /// - /// Position in the source - /// Length of the data to check - /// True if the positional data is valid, false otherwise - public bool SegmentValid(int position, int length) - { - // Validate the data souece - if (!DataSourceIsValid()) - return false; - - // If we have an invalid position - if (position < 0 || position >= GetEndOfFile()) - return false; - - return _dataSource switch - { - DataSource.ByteArray => _byteArrayOffset + position + length <= _byteArrayData!.Length, - DataSource.Stream => position + length <= _streamData!.Length, - - // Everything else is invalid - _ => false, - }; - } - - /// - /// Read data from the source - /// - /// Position in the source to read from - /// Length of the requested data - /// Byte array containing the requested data, null on error - public byte[]? ReadFromDataSource(int position, int length) - { - // Validate the data source - if (!DataSourceIsValid()) - return null; - - // Validate the requested segment - if (!SegmentValid(position, length)) - return null; - - try - { - // Read and return the data - byte[]? sectionData = null; - switch (_dataSource) - { - case DataSource.ByteArray: - sectionData = new byte[length]; - Array.Copy(_byteArrayData!, _byteArrayOffset + position, sectionData, 0, length); - break; - - case DataSource.Stream: - lock (_streamDataLock) - { - long currentLocation = _streamData!.Position; - _streamData.Seek(position, SeekOrigin.Begin); - sectionData = _streamData.ReadBytes(length); - _streamData.Seek(currentLocation, SeekOrigin.Begin); - break; - } - } - - return sectionData; - - } - catch - { - // Absorb the error - return null; - } - } - - /// - /// Read string data from the source - /// - /// Position in the source to read from - /// Length of the requested data - /// Number of characters needed to be a valid string - /// String list containing the requested data, null on error - public List? ReadStringsFromDataSource(int position, int length, int charLimit = 5) - { - // Read the data as a byte array first - byte[]? sourceData = ReadFromDataSource(position, length); - if (sourceData == null) - return null; - - // Check for ASCII strings - var asciiStrings = ReadStringsWithEncoding(sourceData, charLimit, Encoding.ASCII); - - // Check for UTF-8 strings - // We are limiting the check for Unicode characters with a second byte of 0x00 for now - var utf8Strings = ReadStringsWithEncoding(sourceData, charLimit, Encoding.UTF8); - - // Check for Unicode strings - // We are limiting the check for Unicode characters with a second byte of 0x00 for now - var unicodeStrings = ReadStringsWithEncoding(sourceData, charLimit, Encoding.Unicode); - - // Ignore duplicate strings across encodings - List sourceStrings = [.. asciiStrings, .. utf8Strings, .. unicodeStrings]; - - // Sort the strings and return - sourceStrings.Sort(); - return sourceStrings; - } - - /// - /// Get the ending offset of the source - /// - /// Value greater than 0 for a valid end of file, -1 on error - public int GetEndOfFile() - { - // Validate the data souece - if (!DataSourceIsValid()) - return -1; - - // Return the effective endpoint - return _dataSource switch - { - DataSource.ByteArray => _byteArrayData!.Length - _byteArrayOffset, - DataSource.Stream => (int)_streamData!.Length, - _ => -1, - }; - } - - /// - /// Read string data from the source with an encoding - /// - /// Byte array representing the source data - /// Number of characters needed to be a valid string - /// Character encoding to use for checking - /// String list containing the requested data, empty on error - /// TODO: Move to IO? -#if NET20 - private static List ReadStringsWithEncoding(byte[] sourceData, int charLimit, Encoding encoding) -#else - private static HashSet ReadStringsWithEncoding(byte[] sourceData, int charLimit, Encoding encoding) -#endif - { - // If we have an invalid character limit, default to 5 - if (charLimit <= 0) - charLimit = 5; - - // Create the string hash set to return -#if NET20 - var sourceStrings = new List(); -#else - var sourceStrings = new HashSet(); -#endif - - // Setup cached data - int sourceDataIndex = 0; - List cachedChars = []; - - // Check for strings - while (sourceDataIndex < sourceData.Length) - { - // Read the next character - char ch = encoding.GetChars(sourceData, sourceDataIndex, 1)[0]; - - // If we have a control character or an invalid byte - bool isValid = !char.IsControl(ch) && (ch & 0xFF00) == 0; - if (!isValid) - { - // If we have no cached string - if (cachedChars.Count == 0) - { - sourceDataIndex++; - continue; - } - - // If we have a cached string greater than the limit - if (cachedChars.Count >= charLimit) - sourceStrings.Add(new string([.. cachedChars])); - - cachedChars.Clear(); - sourceDataIndex++; - continue; - } - - // If a long repeating string is found, discard it - if (cachedChars.Count >= 64 && cachedChars.TrueForAll(c => c == cachedChars[0])) - { - cachedChars.Clear(); - sourceDataIndex++; - continue; - } - - // Append the character to the cached string - cachedChars.Add(ch); - sourceDataIndex++; - } - - // If we have a cached string greater than the limit - if (cachedChars.Count >= charLimit) - { - // Get the string from the cached characters - string cachedString = new([.. cachedChars]); - cachedString = cachedString.Trim(); - - // Only include trimmed strings over the limit - if (cachedString.Length >= charLimit) - sourceStrings.Add(cachedString); - } - - return sourceStrings; - } - - #endregion - - #region JSON Export - -#if NETCOREAPP - /// - /// Export the item information as JSON - /// - public override string ExportJSON() => System.Text.Json.JsonSerializer.Serialize(Model, _jsonSerializerOptions); -#endif - - #endregion - } } diff --git a/SabreTools.Serialization/Wrappers/WrapperBaseT.cs b/SabreTools.Serialization/Wrappers/WrapperBaseT.cs new file mode 100644 index 00000000..44cd1a67 --- /dev/null +++ b/SabreTools.Serialization/Wrappers/WrapperBaseT.cs @@ -0,0 +1,377 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using SabreTools.IO.Extensions; +using SabreTools.Serialization.Interfaces; + +namespace SabreTools.Serialization.Wrappers +{ + public abstract class WrapperBase : WrapperBase, IWrapper + { + #region Properties + + /// + public T GetModel() => Model; + + /// + /// Internal model + /// + public T Model { get; private set; } + + /// + /// Length of the underlying data + /// + public long Length + { + get + { + return _dataSource switch + { + DataSource.ByteArray => _byteArrayData!.Length - _byteArrayOffset, + DataSource.Stream => _streamData!.Length, + + // Everything else is invalid + _ => -1, + }; + } + } + + #endregion + + #region Instance Variables + + /// + /// Source of the original data + /// + protected DataSource _dataSource = DataSource.UNKNOWN; + + /// + /// Lock object for reading from the source + /// + private readonly object _streamDataLock = new(); + + /// + /// Source byte array data + /// + /// This is only populated if is + protected byte[]? _byteArrayData = null; + + /// + /// Source byte array data offset + /// + /// This is only populated if is + protected int _byteArrayOffset = -1; + + /// + /// Source Stream data + /// + /// This is only populated if is + protected Stream? _streamData = null; + +#if NETCOREAPP + /// + /// JSON serializer options for output printing + /// + protected System.Text.Json.JsonSerializerOptions _jsonSerializerOptions + { + get + { +#if NETCOREAPP3_1 + var serializer = new System.Text.Json.JsonSerializerOptions { WriteIndented = true }; +#else + var serializer = new System.Text.Json.JsonSerializerOptions { IncludeFields = true, WriteIndented = true }; +#endif + serializer.Converters.Add(new ConcreteAbstractSerializer()); + serializer.Converters.Add(new ConcreteInterfaceSerializer()); + serializer.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter()); + return serializer; + } + } +#endif + + #endregion + + #region Constructors + + /// + /// Construct a new instance of the wrapper from a byte array + /// + protected WrapperBase(T? model, byte[]? data, int offset) + { + if (model == null) + throw new ArgumentNullException(nameof(model)); + if (data == null) + throw new ArgumentNullException(nameof(data)); + if (offset < 0 || offset >= data.Length) + throw new ArgumentOutOfRangeException(nameof(offset)); + + Model = model; + _dataSource = DataSource.ByteArray; + _byteArrayData = data; + _byteArrayOffset = offset; + } + + /// + /// Construct a new instance of the wrapper from a Stream + /// + protected WrapperBase(T? model, Stream? data) + { + if (model == null) + throw new ArgumentNullException(nameof(model)); + if (data == null) + throw new ArgumentNullException(nameof(data)); + if (!data.CanSeek || !data.CanRead) + throw new ArgumentOutOfRangeException(nameof(data)); + + Model = model; + _dataSource = DataSource.Stream; + _streamData = data; + } + + #endregion + + #region Data + + /// + /// Validate the backing data source + /// + /// True if the data source is valid, false otherwise + public bool DataSourceIsValid() + { + return _dataSource switch + { + // Byte array data requires both a valid array and offset + DataSource.ByteArray => _byteArrayData != null && _byteArrayOffset >= 0, + + // Stream data requires both a valid stream + DataSource.Stream => _streamData != null && _streamData.CanRead && _streamData.CanSeek, + + // Everything else is invalid + _ => false, + }; + } + + /// + /// Check if a data segment is valid in the data source + /// + /// Position in the source + /// Length of the data to check + /// True if the positional data is valid, false otherwise + public bool SegmentValid(int position, int length) + { + // Validate the data souece + if (!DataSourceIsValid()) + return false; + + // If we have an invalid position + if (position < 0 || position >= GetEndOfFile()) + return false; + + return _dataSource switch + { + DataSource.ByteArray => _byteArrayOffset + position + length <= _byteArrayData!.Length, + DataSource.Stream => position + length <= _streamData!.Length, + + // Everything else is invalid + _ => false, + }; + } + + /// + /// Read data from the source + /// + /// Position in the source to read from + /// Length of the requested data + /// Byte array containing the requested data, null on error + public byte[]? ReadFromDataSource(int position, int length) + { + // Validate the data source + if (!DataSourceIsValid()) + return null; + + // Validate the requested segment + if (!SegmentValid(position, length)) + return null; + + try + { + // Read and return the data + byte[]? sectionData = null; + switch (_dataSource) + { + case DataSource.ByteArray: + sectionData = new byte[length]; + Array.Copy(_byteArrayData!, _byteArrayOffset + position, sectionData, 0, length); + break; + + case DataSource.Stream: + lock (_streamDataLock) + { + long currentLocation = _streamData!.Position; + _streamData.Seek(position, SeekOrigin.Begin); + sectionData = _streamData.ReadBytes(length); + _streamData.Seek(currentLocation, SeekOrigin.Begin); + break; + } + } + + return sectionData; + + } + catch + { + // Absorb the error + return null; + } + } + + /// + /// Read string data from the source + /// + /// Position in the source to read from + /// Length of the requested data + /// Number of characters needed to be a valid string + /// String list containing the requested data, null on error + public List? ReadStringsFromDataSource(int position, int length, int charLimit = 5) + { + // Read the data as a byte array first + byte[]? sourceData = ReadFromDataSource(position, length); + if (sourceData == null) + return null; + + // Check for ASCII strings + var asciiStrings = ReadStringsWithEncoding(sourceData, charLimit, Encoding.ASCII); + + // Check for UTF-8 strings + // We are limiting the check for Unicode characters with a second byte of 0x00 for now + var utf8Strings = ReadStringsWithEncoding(sourceData, charLimit, Encoding.UTF8); + + // Check for Unicode strings + // We are limiting the check for Unicode characters with a second byte of 0x00 for now + var unicodeStrings = ReadStringsWithEncoding(sourceData, charLimit, Encoding.Unicode); + + // Ignore duplicate strings across encodings + List sourceStrings = [.. asciiStrings, .. utf8Strings, .. unicodeStrings]; + + // Sort the strings and return + sourceStrings.Sort(); + return sourceStrings; + } + + /// + /// Get the ending offset of the source + /// + /// Value greater than 0 for a valid end of file, -1 on error + public int GetEndOfFile() + { + // Validate the data souece + if (!DataSourceIsValid()) + return -1; + + // Return the effective endpoint + return _dataSource switch + { + DataSource.ByteArray => _byteArrayData!.Length - _byteArrayOffset, + DataSource.Stream => (int)_streamData!.Length, + _ => -1, + }; + } + + /// + /// Read string data from the source with an encoding + /// + /// Byte array representing the source data + /// Number of characters needed to be a valid string + /// Character encoding to use for checking + /// String list containing the requested data, empty on error + /// TODO: Move to IO? +#if NET20 + private static List ReadStringsWithEncoding(byte[] sourceData, int charLimit, Encoding encoding) +#else + private static HashSet ReadStringsWithEncoding(byte[] sourceData, int charLimit, Encoding encoding) +#endif + { + // If we have an invalid character limit, default to 5 + if (charLimit <= 0) + charLimit = 5; + + // Create the string hash set to return +#if NET20 + var sourceStrings = new List(); +#else + var sourceStrings = new HashSet(); +#endif + + // Setup cached data + int sourceDataIndex = 0; + List cachedChars = []; + + // Check for strings + while (sourceDataIndex < sourceData.Length) + { + // Read the next character + char ch = encoding.GetChars(sourceData, sourceDataIndex, 1)[0]; + + // If we have a control character or an invalid byte + bool isValid = !char.IsControl(ch) && (ch & 0xFF00) == 0; + if (!isValid) + { + // If we have no cached string + if (cachedChars.Count == 0) + { + sourceDataIndex++; + continue; + } + + // If we have a cached string greater than the limit + if (cachedChars.Count >= charLimit) + sourceStrings.Add(new string([.. cachedChars])); + + cachedChars.Clear(); + sourceDataIndex++; + continue; + } + + // If a long repeating string is found, discard it + if (cachedChars.Count >= 64 && cachedChars.TrueForAll(c => c == cachedChars[0])) + { + cachedChars.Clear(); + sourceDataIndex++; + continue; + } + + // Append the character to the cached string + cachedChars.Add(ch); + sourceDataIndex++; + } + + // If we have a cached string greater than the limit + if (cachedChars.Count >= charLimit) + { + // Get the string from the cached characters + string cachedString = new([.. cachedChars]); + cachedString = cachedString.Trim(); + + // Only include trimmed strings over the limit + if (cachedString.Length >= charLimit) + sourceStrings.Add(cachedString); + } + + return sourceStrings; + } + + #endregion + + #region JSON Export + +#if NETCOREAPP + /// + /// Export the item information as JSON + /// + public override string ExportJSON() => System.Text.Json.JsonSerializer.Serialize(Model, _jsonSerializerOptions); +#endif + + #endregion + } +}