You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Darwinism/Google.Protobuf/Collections/RepeatedField.vb

586 lines
23 KiB

#Region "Copyright notice and license"
' Protocol Buffers - Google's data interchange format
' Copyright 2015 Google Inc. All rights reserved.
' https://developers.google.com/protocol-buffers/
'
' Redistribution and use in source and binary forms, with or without
' modification, are permitted provided that the following conditions are
' met:
'
' * Redistributions of source code must retain the above copyright
' notice, this list of conditions and the following disclaimer.
' * Redistributions in binary form must reproduce the above
' copyright notice, this list of conditions and the following disclaimer
' in the documentation and/or other materials provided with the
' distribution.
' * Neither the name of Google Inc. nor the names of its
' contributors may be used to endorse or promote products derived from
' this software without specific prior written permission.
'
' THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
' "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
' LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
' A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
' OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
' SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
' LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
' DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
' THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
' (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
' OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#End Region
Imports System
Imports System.Collections
Imports System.Collections.Generic
Imports System.IO
Namespace Google.Protobuf.Collections
''' <summary>
''' The contents of a repeated field: essentially, a collection with some extra
''' restrictions (no null values) and capabilities (deep cloning).
''' </summary>
''' <remarks>
''' This implementation does not generally prohibit the use of types which are not
''' supported by Protocol Buffers but nor does it guarantee that all operations will work in such cases.
''' </remarks>
''' <typeparam name="T">The element type of the repeated field.</typeparam>
Public NotInheritable Class RepeatedField(Of T)
Implements IList(Of T), IList, IDeepCloneable(Of RepeatedField(Of T)), IEquatable(Of RepeatedField(Of T))
Private Shared ReadOnly EmptyArray As T() = New T(-1) {}
Private Const MinArraySize As Integer = 8
Private array As T() = EmptyArray
Private countField As Integer = 0
Public Sub New()
End Sub
Sub New(data As IEnumerable(Of T))
For Each item As T In data
Call Add(item)
Next
End Sub
''' <summary>
''' Creates a deep clone of this repeated field.
''' </summary>
''' <remarks>
''' If the field type is
''' a message type, each element is also cloned; otherwise, it is
''' assumed that the field type is primitive (including string and
''' bytes, both of which are immutable) and so a simple copy is
''' equivalent to a deep clone.
''' </remarks>
''' <returns>A deep clone of this repeated field.</returns>
Public Function Clone() As RepeatedField(Of T) Implements IDeepCloneable(Of RepeatedField(Of T)).Clone
Dim lClone As RepeatedField(Of T) = New RepeatedField(Of T)()
If array IsNot EmptyArray Then
lClone.array = CType(array.Clone(), T())
Dim cloneableArray As IDeepCloneable(Of T)() = TryCast(lClone.array, IDeepCloneable(Of T)())
If cloneableArray IsNot Nothing Then
For i = 0 To countField - 1
lClone.array(i) = cloneableArray(i).Clone()
Next
End If
End If
lClone.countField = countField
Return lClone
End Function
''' <summary>
''' Adds the entries from the given input stream, decoding them with the specified codec.
''' </summary>
''' <param name="input">The input stream to read from.</param>
''' <param name="codec">The codec to use in order to read each entry.</param>
Public Sub AddEntriesFrom(input As CodedInputStream, codec As FieldCodecType(Of T))
' TODO: Inline some of the Add code, so we can avoid checking the size on every
' iteration.
Dim tag = input.LastTag
Dim reader = codec.ValueReader
' Non-nullable value types can be packed or not.
If FieldCodecType(Of T).IsPackedRepeatedField(tag) Then
Dim length As Integer = input.ReadLength()
If length > 0 Then
Dim oldLimit = input.PushLimit(length)
While Not input.ReachedLimit
Add(reader(input))
End While
input.PopLimit(oldLimit)
' Empty packed field. Odd, but valid - just ignore.
End If
Else
' Not packed... (possibly not packable)
Do
Add(reader(input))
Loop While input.MaybeConsumeTag(tag)
End If
End Sub
''' <summary>
''' Calculates the size of this collection based on the given codec.
''' </summary>
''' <param name="codec">The codec to use when encoding each field.</param>
''' <returns>The number of bytes that would be written to a <see cref="CodedOutputStream"/> by <see cref="WriteTo"/>,
''' using the same codec.</returns>
Public Function CalculateSize(codec As FieldCodecType(Of T)) As Integer
If countField = 0 Then
Return 0
End If
Dim tag = codec.Tag
If codec.PackedRepeatedField Then
Dim dataSize = CalculatePackedDataSize(codec)
Return CodedOutputStream.ComputeRawVarint32Size(tag) + CodedOutputStream.ComputeLengthSize(dataSize) + dataSize
Else
Dim sizeCalculator = codec.ValueSizeCalculator
Dim size = countField * CodedOutputStream.ComputeRawVarint32Size(tag)
For i = 0 To countField - 1
size += sizeCalculator(array(i))
Next
Return size
End If
End Function
Private Function CalculatePackedDataSize(codec As FieldCodecType(Of T)) As Integer
Dim fixedSize = codec.FixedSize
If fixedSize = 0 Then
Dim calculator = codec.ValueSizeCalculator
Dim tmp = 0
For i = 0 To countField - 1
tmp += calculator(array(i))
Next
Return tmp
Else
Return fixedSize * Count
End If
End Function
''' <summary>
''' Writes the contents of this collection to the given <see cref="CodedOutputStream"/>,
''' encoding each value using the specified codec.
''' </summary>
''' <param name="output">The output stream to write to.</param>
''' <param name="codec">The codec to use when encoding each value.</param>
Public Sub WriteTo(output As CodedOutputStream, codec As FieldCodecType(Of T))
If countField = 0 Then
Return
End If
Dim writer = codec.ValueWriter
Dim tag = codec.Tag
If codec.PackedRepeatedField Then
' Packed primitive type
Dim size As UInteger = CalculatePackedDataSize(codec)
output.WriteTag(tag)
output.WriteRawVarint32(size)
For i = 0 To countField - 1
writer(output, array(i))
Next
Else
' Not packed: a simple tag/value pair for each value.
' Can't use codec.WriteTagAndValue, as that omits default values.
For i = 0 To countField - 1
output.WriteTag(tag)
writer(output, array(i))
Next
End If
End Sub
Private Sub EnsureSize(size As Integer)
If array.Length < size Then
size = Math.Max(size, MinArraySize)
Dim newSize = Math.Max(array.Length * 2, size)
Dim tmp = New T(newSize - 1) {}
System.Array.Copy(array, 0, tmp, 0, array.Length)
array = tmp
End If
End Sub
''' <summary>
''' Adds the specified item to the collection.
''' </summary>
''' <param name="item">The item to add.</param>
Public Sub Add(item As T) Implements ICollection(Of T).Add
CheckNotNullUnconstrained(item, NameOf(item))
EnsureSize(countField + 1)
array(Math.Min(Threading.Interlocked.Increment(countField), countField - 1)) = item
End Sub
''' <summary>
''' Removes all items from the collection.
''' </summary>
Public Sub Clear() Implements ICollection(Of T).Clear, IList.Clear
array = EmptyArray
countField = 0
End Sub
''' <summary>
''' Determines whether this collection contains the given item.
''' </summary>
''' <param name="item">The item to find.</param>
''' <returns><c>true</c> if this collection contains the given item; <c>false</c> otherwise.</returns>
Public Function Contains(item As T) As Boolean Implements ICollection(Of T).Contains
Return IndexOf(item) <> -1
End Function
''' <summary>
''' Copies this collection to the given array.
''' </summary>
''' <param name="array">The array to copy to.</param>
''' <param name="arrayIndex">The first index of the array to copy to.</param>
Public Sub CopyTo(array As T(), arrayIndex As Integer) Implements ICollection(Of T).CopyTo
System.Array.Copy(Me.array, 0, array, arrayIndex, countField)
End Sub
''' <summary>
''' Removes the specified item from the collection
''' </summary>
''' <param name="item">The item to remove.</param>
''' <returns><c>true</c> if the item was found and removed; <c>false</c> otherwise.</returns>
Public Function Remove(item As T) As Boolean Implements ICollection(Of T).Remove
Dim index = IndexOf(item)
If index = -1 Then
Return False
End If
System.Array.Copy(array, index + 1, array, index, countField - index - 1)
countField -= 1
array(countField) = Nothing
Return True
End Function
''' <summary>
''' Gets the number of elements contained in the collection.
''' </summary>
Public ReadOnly Property Count As Integer Implements ICollection(Of T).Count, ICollection.Count
Get
Return countField
End Get
End Property
''' <summary>
''' Gets a value indicating whether the collection is read-only.
''' </summary>
Public ReadOnly Property IsReadOnly As Boolean Implements ICollection(Of T).IsReadOnly, IList.IsReadOnly
Get
Return False
End Get
End Property
''' <summary>
''' Adds all of the specified values into this collection.
''' </summary>
''' <param name="values">The values to add to this collection.</param>
Public Sub AddRange(values As IEnumerable(Of T))
CheckNotNull(values, NameOf(values))
' Optimization 1: If the collection we're adding is already a RepeatedField<T>,
' we know the values are valid.
Dim otherRepeatedField = TryCast(values, RepeatedField(Of T))
If otherRepeatedField IsNot Nothing Then
EnsureSize(countField + otherRepeatedField.countField)
System.Array.Copy(otherRepeatedField.array, 0, array, countField, otherRepeatedField.countField)
countField += otherRepeatedField.countField
Return
End If
' Optimization 2: The collection is an ICollection, so we can expand
' just once and ask the collection to copy itself into the array.
Dim collection = TryCast(values, ICollection)
If collection IsNot Nothing Then
Dim extraCount = collection.Count
' For reference types and nullable value types, we need to check that there are no nulls
' present. (This isn't a thread-safe approach, but we don't advertise this is thread-safe.)
' We expect the JITter to optimize this test to true/false, so it's effectively conditional
' specialization.
If Nothing Is Nothing Then
' TODO: Measure whether iterating once to check and then letting the collection copy
' itself is faster or slower than iterating and adding as we go. For large
' collections this will not be great in terms of cache usage... but the optimized
' copy may be significantly faster than doing it one at a time.
For Each item As T In collection
If item Is Nothing Then
Throw New ArgumentException("Sequence contained null element", NameOf(values))
End If
Next
End If
EnsureSize(countField + extraCount)
collection.CopyTo(array, countField)
countField += extraCount
Return
End If
' We *could* check for ICollection<T> as well, but very very few collections implement
' ICollection<T> but not ICollection. (HashSet<T> does, for one...)
' Fall back to a slower path of adding items one at a time.
For Each item As T In values
Add(item)
Next
End Sub
''' <summary>
''' Adds all of the specified values into this collection. This method is present to
''' allow repeated fields to be constructed from queries within collection initializers.
''' Within non-collection-initializer code, consider using the equivalent <see cref="AddRange"/>
''' method instead for clarity.
''' </summary>
''' <param name="values">The values to add to this collection.</param>
Public Sub Add(values As IEnumerable(Of T))
AddRange(values)
End Sub
''' <summary>
''' Returns an enumerator that iterates through the collection.
''' </summary>
''' <returns>
''' An enumerator that can be used to iterate through the collection.
''' </returns>
Public Iterator Function GetEnumerator() As IEnumerator(Of T) Implements IEnumerable(Of T).GetEnumerator
For i = 0 To countField - 1
Yield array(i)
Next
End Function
''' <summary>
''' Determines whether the specified <see cref="System.Object"/>, is equal to this instance.
''' </summary>
''' <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
''' <returns>
''' <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
''' </returns>
Public Overrides Function Equals(obj As Object) As Boolean
Return Equals(TryCast(obj, RepeatedField(Of T)))
End Function
''' <summary>
''' Returns an enumerator that iterates through a collection.
''' </summary>
''' <returns>
''' An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
''' </returns>
Private Function GetEnumerator1() As IEnumerator Implements IEnumerable.GetEnumerator
Return GetEnumerator()
End Function
''' <summary>
''' Returns a hash code for this instance.
''' </summary>
''' <returns>
''' A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
''' </returns>
Public Overrides Function GetHashCode() As Integer
Dim hash = 0
For i = 0 To countField - 1
hash = hash * 31 + array(i).GetHashCode()
Next
Return hash
End Function
''' <summary>
''' Compares this repeated field with another for equality.
''' </summary>
''' <param name="other">The repeated field to compare this with.</param>
''' <returns><c>true</c> if <paramrefname="other"/> refers to an equal repeated field; <c>false</c> otherwise.</returns>
Public Overloads Function Equals(other As RepeatedField(Of T)) As Boolean Implements IEquatable(Of RepeatedField(Of T)).Equals
If ReferenceEquals(other, Nothing) Then
Return False
End If
If ReferenceEquals(other, Me) Then
Return True
End If
If other.Count <> Count Then
Return False
End If
Dim comparer = EqualityComparer(Of T).Default
For i = 0 To countField - 1
If Not comparer.Equals(array(i), other.array(i)) Then
Return False
End If
Next
Return True
End Function
''' <summary>
''' Returns the index of the given item within the collection, or -1 if the item is not
''' present.
''' </summary>
''' <param name="item">The item to find in the collection.</param>
''' <returns>The zero-based index of the item, or -1 if it is not found.</returns>
Public Function IndexOf(item As T) As Integer Implements IList(Of T).IndexOf
CheckNotNullUnconstrained(item, NameOf(item))
Dim comparer = EqualityComparer(Of T).Default
For i = 0 To countField - 1
If comparer.Equals(array(i), item) Then
Return i
End If
Next
Return -1
End Function
''' <summary>
''' Inserts the given item at the specified index.
''' </summary>
''' <param name="index">The index at which to insert the item.</param>
''' <param name="item">The item to insert.</param>
Public Sub Insert(index As Integer, item As T) Implements IList(Of T).Insert
CheckNotNullUnconstrained(item, NameOf(item))
If index < 0 OrElse index > countField Then
Throw New ArgumentOutOfRangeException(NameOf(index))
End If
EnsureSize(countField + 1)
System.Array.Copy(array, index, array, index + 1, countField - index)
array(index) = item
countField += 1
End Sub
''' <summary>
''' Removes the item at the given index.
''' </summary>
''' <param name="index">The zero-based index of the item to remove.</param>
Public Sub RemoveAt(index As Integer) Implements IList(Of T).RemoveAt, IList.RemoveAt
If index < 0 OrElse index >= countField Then
Throw New ArgumentOutOfRangeException(NameOf(index))
End If
System.Array.Copy(array, index + 1, array, index, countField - index - 1)
countField -= 1
array(countField) = Nothing
End Sub
''' <summary>
''' Returns a string representation of this repeated field, in the same
''' way as it would be represented by the default JSON formatter.
''' </summary>
Public Overrides Function ToString() As String
Dim writer = New StringWriter()
JsonFormatter.Default.WriteList(writer, Me)
Return writer.ToString()
End Function
''' <summary>
''' Gets or sets the item at the specified index.
''' </summary>
''' <value>
''' The element at the specified index.
''' </value>
''' <param name="index">The zero-based index of the element to get or set.</param>
''' <returns>The item at the specified index.</returns>
Default Public Overloads Property Item(index As Integer) As T Implements IList(Of T).Item
Get
If index < 0 OrElse index >= countField Then
Throw New ArgumentOutOfRangeException(NameOf(index))
End If
Return array(index)
End Get
Set(value As T)
If index < 0 OrElse index >= countField Then
Throw New ArgumentOutOfRangeException(NameOf(index))
End If
CheckNotNullUnconstrained(value, NameOf(value))
array(index) = value
End Set
End Property
#Region "Explicit interface implementation for IList and ICollection."
Private ReadOnly Property IsFixedSize As Boolean Implements IList.IsFixedSize
Get
Return False
End Get
End Property
Private Sub CopyTo1(array As Array, index As Integer) Implements ICollection.CopyTo
Array.Copy(Me.array, 0, array, index, countField)
End Sub
Private ReadOnly Property IsSynchronized As Boolean Implements ICollection.IsSynchronized
Get
Return False
End Get
End Property
Private ReadOnly Property SyncRoot As Object Implements ICollection.SyncRoot
Get
Return Me
End Get
End Property
Private Property Item1(index As Integer) As Object Implements IList.Item
Get
Return Me(index)
End Get
Set(value As Object)
Me(index) = CType(value, T)
End Set
End Property
Private Function Add1(value As Object) As Integer Implements IList.Add
Add(CType(value, T))
Return countField - 1
End Function
Private Function Contains1(value As Object) As Boolean Implements IList.Contains
Return TypeOf value Is T AndAlso Contains(value)
End Function
Private Function IndexOf1(value As Object) As Integer Implements IList.IndexOf
If Not (TypeOf value Is T) Then
Return -1
End If
Return IndexOf(value)
End Function
Private Sub Insert1(index As Integer, value As Object) Implements IList.Insert
Insert(index, value)
End Sub
Private Sub Remove1(value As Object) Implements IList.Remove
If Not (TypeOf value Is T) Then
Return
End If
Remove(value)
End Sub
#End Region
End Class
End Namespace