//******************************************************************************************************
// ArrayExtensions.cs - Gbtc
//
// Copyright © 2012, 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:
// ----------------------------------------------------------------------------------------------------
// 09/19/2008 - J. Ritchie Carroll
// Generated original version of source code.
// 12/03/2008 - J. Ritchie Carroll
// Added "Combine" and "IndexOfSequence" overloaded extensions.
// 02/13/2009 - Josh L. Patterson
// Edited Code Comments.
// 09/14/2009 - Stephen C. Wills
// Added new header and license agreement.
// 12/31/2009 - Andrew K. Hill
// Modified the following methods per unit testing:
// BlockCopy(T[], int, int)
// Combine(T[], T[])
// Combine(T[], int, int, T[], int, int)
// Combine(T[][])
// IndexOfSequence(T[], T[])
// IndexOfSequence(T[], T[], int)
// IndexOfSequence(T[], T[], int, int)
// 11/22/2011 - J. Ritchie Carroll
// Added common case array parameter validation extensions
// 12/14/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
using System;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using GSF.IO;
namespace GSF
{
///
/// Defines extension functions related to manipulation.
///
public static class ArrayExtensions
{
///
/// Validates that the specified and are valid within the given .
///
/// Array to validate.
/// 0-based start index into the .
/// Valid number of items within from .
/// is null.
///
/// or is less than 0 -or-
/// and will exceed length.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ValidateParameters(this T[] array, int startIndex, int length)
{
if ((object)array == null || startIndex < 0 || length < 0 || startIndex + length > array.Length)
RaiseValidationError(array, startIndex, length);
}
// This method will raise the actual error - this is needed since .NET will not inline anything that might throw an exception
[MethodImpl(MethodImplOptions.NoInlining)]
private static void RaiseValidationError(T[] array, int startIndex, int length)
{
if ((object)array == null)
throw new ArgumentNullException(nameof(array));
if (startIndex < 0)
throw new ArgumentOutOfRangeException(nameof(startIndex), "cannot be negative");
if (length < 0)
throw new ArgumentOutOfRangeException(nameof(length), "cannot be negative");
if (startIndex + length > array.Length)
throw new ArgumentOutOfRangeException(nameof(length), $"startIndex of {startIndex} and length of {length} will exceed array size of {array.Length}");
}
///
/// Returns a copy of the specified portion of the array.
///
/// Source array.
/// Offset into array.
/// Length of array to copy at offset.
/// A array of data copied from the specified portion of the source array.
///
///
/// Returned array will be extended as needed to make it the specified , but
/// it will never be less than the source array length - .
///
///
/// If an existing array of primitives is already available, using the directly
/// instead of this extension method may be optimal since this method always allocates a new return array.
/// Unlike , however, this function also works with non-primitive types.
///
///
///
/// is outside the range of valid indexes for the source array -or-
/// is less than 0.
///
public static T[] BlockCopy(this T[] array, int startIndex, int length)
{
if ((object)array == null)
throw new ArgumentNullException(nameof(array));
if (startIndex < 0)
throw new ArgumentOutOfRangeException(nameof(startIndex), "cannot be negative");
if (length < 0)
throw new ArgumentOutOfRangeException(nameof(length), "cannot be negative");
if (startIndex >= array.Length)
throw new ArgumentOutOfRangeException(nameof(startIndex), "not a valid index into the array");
T[] copiedBytes = new T[array.Length - startIndex < length ? array.Length - startIndex : length];
if (typeof(T).IsPrimitive)
Buffer.BlockCopy(array, startIndex, copiedBytes, 0, copiedBytes.Length);
else
Array.Copy(array, startIndex, copiedBytes, 0, copiedBytes.Length);
return copiedBytes;
}
///
/// Combines arrays together into a single array.
///
/// Source array.
/// Other array to combine to array.
/// Combined arrays.
///
///
/// Only use this function if you need a copy of the combined arrays, it will be optimal
/// to use the Linq function if you simply need to
/// iterate over the combined arrays.
///
///
/// This function can easily throw an out of memory exception if there is not enough
/// contiguous memory to create an array sized with the combined lengths.
///
///
public static T[] Combine(this T[] source, T[] other)
{
if ((object)source == null)
throw new ArgumentNullException(nameof(source));
if ((object)other == null)
throw new ArgumentNullException(nameof(other));
return source.Combine(0, source.Length, other, 0, other.Length);
}
///
/// Combines specified portions of arrays together into a single array.
///
/// Source array.
/// Offset into array to begin copy.
/// Number of bytes to copy from array.
/// Other array to combine to array.
/// Offset into array to begin copy.
/// Number of bytes to copy from array.
/// Combined specified portions of both arrays.
///
/// or is outside the range of valid indexes for the associated array -or-
/// or is less than 0 -or-
/// or ,
/// and or do not specify a valid section in the associated array.
///
///
///
/// Only use this function if you need a copy of the combined arrays, it will be optimal
/// to use the Linq function if you simply need to
/// iterate over the combined arrays.
///
///
/// This function can easily throw an out of memory exception if there is not enough
/// contiguous memory to create an array sized with the combined lengths.
///
///
public static T[] Combine(this T[] source, int sourceOffset, int sourceCount, T[] other, int otherOffset, int otherCount)
{
if ((object)source == null)
throw new ArgumentNullException(nameof(source));
if ((object)other == null)
throw new ArgumentNullException(nameof(other));
if (sourceOffset < 0)
throw new ArgumentOutOfRangeException(nameof(sourceOffset), "cannot be negative");
if (otherOffset < 0)
throw new ArgumentOutOfRangeException(nameof(otherOffset), "cannot be negative");
if (sourceCount < 0)
throw new ArgumentOutOfRangeException(nameof(sourceCount), "cannot be negative");
if (otherCount < 0)
throw new ArgumentOutOfRangeException(nameof(otherCount), "cannot be negative");
if (sourceOffset >= source.Length)
throw new ArgumentOutOfRangeException(nameof(sourceOffset), "not a valid index into source array");
if (otherOffset >= other.Length)
throw new ArgumentOutOfRangeException(nameof(otherOffset), "not a valid index into other array");
if (sourceOffset + sourceCount > source.Length)
throw new ArgumentOutOfRangeException(nameof(sourceCount), "exceeds source array size");
if (otherOffset + otherCount > other.Length)
throw new ArgumentOutOfRangeException(nameof(otherCount), "exceeds other array size");
// Overflow is possible, but unlikely. Therefore, this is omitted for performance
// if ((int.MaxValue - sourceCount - otherCount) < 0)
// throw new ArgumentOutOfRangeException("sourceCount + otherCount", "exceeds maximum array size");
// Combine arrays together as a single image
T[] combinedBuffer = new T[sourceCount + otherCount];
if (typeof(T).IsPrimitive)
{
Buffer.BlockCopy(source, sourceOffset, combinedBuffer, 0, sourceCount);
Buffer.BlockCopy(other, otherOffset, combinedBuffer, sourceCount, otherCount);
}
else
{
Array.Copy(source, sourceOffset, combinedBuffer, 0, sourceCount);
Array.Copy(other, otherOffset, combinedBuffer, sourceCount, otherCount);
}
return combinedBuffer;
}
///
/// Combines arrays together into a single array.
///
/// Source array.
/// First array to combine to array.
/// Second array to combine to array.
/// Combined arrays.
///
///
/// Only use this function if you need a copy of the combined arrays, it will be optimal
/// to use the Linq function if you simply need to
/// iterate over the combined arrays.
///
///
/// This function can easily throw an out of memory exception if there is not enough
/// contiguous memory to create an array sized with the combined lengths.
///
///
public static T[] Combine(this T[] source, T[] other1, T[] other2)
{
return new[] { source, other1, other2 }.Combine();
}
///
/// Combines arrays together into a single array.
///
/// Source array.
/// First array to combine to array.
/// Second array to combine to array.
/// Third array to combine to array.
/// Combined arrays.
///
///
/// Only use this function if you need a copy of the combined arrays, it will be optimal
/// to use the Linq function if you simply need to
/// iterate over the combined arrays.
///
///
/// This function can easily throw an out of memory exception if there is not enough
/// contiguous memory to create an array sized with the combined lengths.
///
///
public static T[] Combine(this T[] source, T[] other1, T[] other2, T[] other3)
{
return new[] { source, other1, other2, other3 }.Combine();
}
///
/// Combines arrays together into a single array.
///
/// Source array.
/// First array to combine to array.
/// Second array to combine to array.
/// Third array to combine to array.
/// Fourth array to combine to array.
/// Combined arrays.
///
///
/// Only use this function if you need a copy of the combined arrays, it will be optimal
/// to use the Linq function if you simply need to
/// iterate over the combined arrays.
///
///
/// This function can easily throw an out of memory exception if there is not enough
/// contiguous memory to create an array sized with the combined lengths.
///
///
public static T[] Combine(this T[] source, T[] other1, T[] other2, T[] other3, T[] other4)
{
return new[] { source, other1, other2, other3, other4 }.Combine();
}
///
/// Combines array of arrays together into a single array.
///
/// Array of arrays to combine.
/// Combined arrays.
///
///
/// Only use this function if you need a copy of the combined arrays, it will be optimal
/// to use the Linq function if you simply need to
/// iterate over the combined arrays.
///
///
/// This function can easily throw an out of memory exception if there is not enough
/// contiguous memory to create an array sized with the combined lengths.
///
///
public static T[] Combine(this T[][] arrays)
{
if ((object)arrays == null)
throw new ArgumentNullException(nameof(arrays));
int size = arrays.Sum(array => array.Length);
int length, offset = 0;
// Combine arrays together as a single image
T[] combinedBuffer = new T[size];
for (int x = 0; x < arrays.Length; x++)
{
if ((object)arrays[x] == null)
throw new ArgumentNullException("arrays[" + x + "]");
length = arrays[x].Length;
if (length > 0)
{
if (typeof(T).IsPrimitive)
Buffer.BlockCopy(arrays[x], 0, combinedBuffer, offset, length);
else
Array.Copy(arrays[x], 0, combinedBuffer, offset, length);
offset += length;
}
}
return combinedBuffer;
}
///
/// Searches for the specified and returns the index of the first occurrence within the .
///
/// Array to search.
/// Sequence of items to search for.
/// The zero-based index of the first occurrence of the in the , if found; otherwise, -1.
public static int IndexOfSequence(this T[] array, T[] sequenceToFind) where T : IComparable
{
if ((object)array == null)
throw new ArgumentNullException(nameof(array));
if ((object)sequenceToFind == null)
throw new ArgumentNullException(nameof(sequenceToFind));
return array.IndexOfSequence(sequenceToFind, 0, array.Length);
}
///
/// Searches for the specified and returns the index of the first occurrence within the range of elements in the
/// that starts at the specified index.
///
/// Array to search.
/// Sequence of items to search for.
/// Start index in the to start searching.
/// The zero-based index of the first occurrence of the in the , if found; otherwise, -1.
public static int IndexOfSequence(this T[] array, T[] sequenceToFind, int startIndex) where T : IComparable
{
if ((object)array == null)
throw new ArgumentNullException(nameof(array));
if ((object)sequenceToFind == null)
throw new ArgumentNullException(nameof(sequenceToFind));
return array.IndexOfSequence(sequenceToFind, startIndex, array.Length - startIndex);
}
///
/// Searches for the specified and returns the index of the first occurrence within the range of elements in the
/// that starts at the specified index and contains the specified number of elements.
///
/// Array to search.
/// Sequence of items to search for.
/// Start index in the to start searching.
/// Number of bytes in the to search through.
/// The zero-based index of the first occurrence of the in the , if found; otherwise, -1.
///
/// is null or has zero length.
///
///
/// is outside the range of valid indexes for the source array -or-
/// is less than 0.
///
public static int IndexOfSequence(this T[] array, T[] sequenceToFind, int startIndex, int length) where T : IComparable
{
if ((object)array == null)
throw new ArgumentNullException(nameof(array));
if ((object)sequenceToFind == null || sequenceToFind.Length == 0)
throw new ArgumentNullException(nameof(sequenceToFind));
if (startIndex < 0)
throw new ArgumentOutOfRangeException(nameof(startIndex), "cannot be negative");
if (length < 0)
throw new ArgumentOutOfRangeException(nameof(length), "cannot be negative");
if (startIndex >= array.Length)
throw new ArgumentOutOfRangeException(nameof(startIndex), "not a valid index into source array");
if (startIndex + length > array.Length)
throw new ArgumentOutOfRangeException(nameof(length), "exceeds array size");
// Overflow is possible, but unlikely. Therefore, this is omitted for performance
// if ((int.MaxValue - startIndex - length) < 0)
// throw new ArgumentOutOfRangeException("startIndex + length", "exceeds maximum array size");
// Search for first item in the sequence, if this doesn't exist then sequence doesn't exist
int index = Array.IndexOf(array, sequenceToFind[0], startIndex, length);
if (sequenceToFind.Length > 1)
{
bool foundSequence = false;
while (index > -1 && !foundSequence)
{
// See if next bytes in sequence match
for (int x = 1; x < sequenceToFind.Length; x++)
{
// Make sure there's enough array remaining to accommodate this item
if (index + x < startIndex + length)
{
// If sequence doesn't match, search for next first-item
if (array[index + x].CompareTo(sequenceToFind[x]) != 0)
{
index = Array.IndexOf(array, sequenceToFind[0], index + 1, (startIndex + length) - (index + 1));
break;
}
// If each item to find matched, we found the sequence
foundSequence = x == sequenceToFind.Length - 1;
}
else
{
// Ran out of array, return -1
index = -1;
}
}
}
}
return index;
}
/// Returns comparison results of two binary arrays.
/// Source array.
/// Other array to compare to array.
///
/// Note that if both arrays are null the arrays will be considered equal.
/// If one array is null and the other array is not null, the non-null array will be considered larger.
/// If the array lengths are not equal, the array with the larger length will be considered larger.
/// If the array lengths are equal, the arrays will be compared based on content.
///
///
///
/// A signed integer that indicates the relative comparison of array and array.
///
///
///
///
/// Return Value
/// Description
///
/// -
/// Less than zero
/// Source array is less than other array.
///
/// -
/// Zero
/// Source array is equal to other array.
///
/// -
/// Greater than zero
/// Source array is greater than other array.
///
///
///
///
public static int CompareTo(this T[] source, T[] other) where T : IComparable
{
// If both arrays are assumed equal if both are nothing
if ((object)source == null && (object)other == null)
return 0;
// If other array has data and source array is nothing, other array is assumed larger
if ((object)source == null)
return -1;
// If source array has data and other array is nothing, source array is assumed larger
if ((object)other == null)
return 1;
int length1 = source.Length;
int length2 = other.Length;
// If array lengths are unequal, array with largest number of elements is assumed to be largest
if (length1 != length2)
return length1.CompareTo(length2);
int comparison = 0;
// Compares elements of arrays that are of equal size.
for (int x = 0; x < length1; x++)
{
comparison = source[x].CompareTo(other[x]);
if (comparison != 0)
break;
}
return comparison;
}
///
/// Returns comparison results of two binary arrays.
///
/// Source array.
/// Offset into array to begin compare.
/// Other array to compare to array.
/// Offset into array to begin compare.
/// Number of bytes to compare in both arrays.
///
/// Note that if both arrays are null the arrays will be considered equal.
/// If one array is null and the other array is not null, the non-null array will be considered larger.
///
///
///
/// A signed integer that indicates the relative comparison of array and array.
///
///
///
///
/// Return Value
/// Description
///
/// -
/// Less than zero
/// Source array is less than other array.
///
/// -
/// Zero
/// Source array is equal to other array.
///
/// -
/// Greater than zero
/// Source array is greater than other array.
///
///
///
///
///
/// or is outside the range of valid indexes for the associated array -or-
/// is less than 0 -or-
/// or and do not specify a valid section in the associated array.
///
public static int CompareTo(this T[] source, int sourceOffset, T[] other, int otherOffset, int count) where T : IComparable
{
// If both arrays are assumed equal if both are nothing
if ((object)source == null && (object)other == null)
return 0;
// If other array has data and source array is nothing, other array is assumed larger
if ((object)source == null)
return -1;
// If source array has data and other array is nothing, source array is assumed larger
if ((object)other == null)
return 1;
if (sourceOffset < 0)
throw new ArgumentOutOfRangeException(nameof(sourceOffset), "cannot be negative");
if (otherOffset < 0)
throw new ArgumentOutOfRangeException(nameof(otherOffset), "cannot be negative");
if (count < 0)
throw new ArgumentOutOfRangeException(nameof(count), "cannot be negative");
if (sourceOffset >= source.Length)
throw new ArgumentOutOfRangeException(nameof(sourceOffset), "not a valid index into source array");
if (otherOffset >= other.Length)
throw new ArgumentOutOfRangeException(nameof(otherOffset), "not a valid index into other array");
if (sourceOffset + count > source.Length)
throw new ArgumentOutOfRangeException(nameof(count), "exceeds source array size");
if (otherOffset + count > other.Length)
throw new ArgumentOutOfRangeException(nameof(count), "exceeds other array size");
// Overflow is possible, but unlikely. Therefore, this is omitted for performance
// if ((int.MaxValue - sourceOffset - count) < 0)
// throw new ArgumentOutOfRangeException("sourceOffset + count", "exceeds maximum array size");
// Overflow is possible, but unlikely. Therefore, this is omitted for performance
// if ((int.MaxValue - otherOffset - count) < 0)
// throw new ArgumentOutOfRangeException("sourceOffset + count", "exceeds maximum array size");
int comparison = 0;
// Compares elements of arrays that are of equal size.
for (int x = 0; x < count; x++)
{
comparison = source[sourceOffset + x].CompareTo(other[otherOffset + x]);
if (comparison != 0)
break;
}
return comparison;
}
// Handling byte arrays as a special case for combining multiple buffers since this can
// use a block allocated memory stream
///
/// Combines buffers together as a single image.
///
/// Source buffer.
/// First buffer to combine to buffer.
/// Second buffer to combine to buffer.
/// Combined buffers.
/// Cannot create a byte array with more than 2,147,483,591 elements.
///
/// Only use this function if you need a copy of the combined buffers, it will be optimal
/// to use the Linq function if you simply need to
/// iterate over the combined buffers.
///
public static byte[] Combine(this byte[] source, byte[] other1, byte[] other2)
{
return new[] { source, other1, other2 }.Combine();
}
///
/// Combines buffers together as a single image.
///
/// Source buffer.
/// First buffer to combine to buffer.
/// Second buffer to combine to buffer.
/// Third buffer to combine to buffer.
/// Combined buffers.
/// Cannot create a byte array with more than 2,147,483,591 elements.
///
/// Only use this function if you need a copy of the combined buffers, it will be optimal
/// to use the Linq function if you simply need to
/// iterate over the combined buffers.
///
public static byte[] Combine(this byte[] source, byte[] other1, byte[] other2, byte[] other3)
{
return new[] { source, other1, other2, other3 }.Combine();
}
///
/// Combines buffers together as a single image.
///
/// Source buffer.
/// First buffer to combine to buffer.
/// Second buffer to combine to buffer.
/// Third buffer to combine to buffer.
/// Fourth buffer to combine to buffer.
/// Combined buffers.
/// Cannot create a byte array with more than 2,147,483,591 elements.
///
/// Only use this function if you need a copy of the combined buffers, it will be optimal
/// to use the Linq function if you simply need to
/// iterate over the combined buffers.
///
public static byte[] Combine(this byte[] source, byte[] other1, byte[] other2, byte[] other3, byte[] other4)
{
return new[] { source, other1, other2, other3, other4 }.Combine();
}
///
/// Combines an array of buffers together as a single image.
///
/// Array of byte buffers.
/// Combined buffers.
/// Cannot create a byte array with more than 2,147,483,591 elements.
///
/// Only use this function if you need a copy of the combined buffers, it will be optimal
/// to use the Linq function if you simply need to
/// iterate over the combined buffers.
///
public static byte[] Combine(this byte[][] buffers)
{
if ((object)buffers == null)
throw new ArgumentNullException(nameof(buffers));
using (BlockAllocatedMemoryStream combinedBuffer = new BlockAllocatedMemoryStream())
{
// Combine all currently queued buffers
for (int x = 0; x < buffers.Length; x++)
{
if ((object)buffers[x] == null)
throw new ArgumentNullException("buffers[" + x + "]");
combinedBuffer.Write(buffers[x], 0, buffers[x].Length);
}
// return combined data buffers
return combinedBuffer.ToArray();
}
}
///
/// Reads a structure from a byte array.
///
/// Type of structure to read.
/// Bytes containing structure.
/// A structure from .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe T ReadStructure(this byte[] bytes) where T : struct
{
T structure;
fixed (byte* ptrToBytes = bytes)
{
structure = (T)Marshal.PtrToStructure(new IntPtr(ptrToBytes), typeof(T));
}
return structure;
}
///
/// Reads a structure from a .
///
/// Type of structure to read.
/// positioned at desired structure.
/// A structure read from .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T ReadStructure(this BinaryReader reader) where T : struct
{
return reader.ReadBytes(Marshal.SizeOf(typeof(T))).ReadStructure();
}
}
}