#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 Google.Protobuf.Reflection Imports Microsoft.VisualBasic.Language Imports System Imports System.Collections Imports System.Collections.Generic Imports System.IO Imports System.Linq Imports System.Runtime.InteropServices Namespace Google.Protobuf.Collections ''' ''' Representation of a map field in a Protocol Buffer message. ''' ''' Key type in the map. Must be a type supported by Protocol Buffer map keys. ''' Value type in the map. Must be a type supported by Protocol Buffers. ''' ''' ''' For string keys, the equality comparison is provided by . ''' ''' ''' Null values are not permitted in the map, either for wrapper types or regular messages. ''' If a map is deserialized from a data stream and the value is missing from an entry, a default value ''' is created instead. For primitive types, that is the regular default value (0, the empty string and so ''' on); for message types, an empty instance of the message is created, as if the map entry contained a 0-length ''' encoded value for the field. ''' ''' ''' This implementation does not generally prohibit the use of key/value types which are not ''' supported by Protocol Buffers (e.g. using a key type of byte) but nor does it guarantee ''' that all operations will work in such cases. ''' ''' ''' The order in which entries are returned when iterating over this object is undefined, and may change ''' in future versions. ''' ''' Public NotInheritable Class MapField(Of TKey, TValue) Implements IDeepCloneable(Of MapField(Of TKey, TValue)), IDictionary(Of TKey, TValue), IEquatable(Of MapField(Of TKey, TValue)), IDictionary ' TODO: Don't create the map/list until we have an entry. (Assume many maps will be empty.) Private ReadOnly map As Dictionary(Of TKey, LinkedListNode(Of KeyValuePair(Of TKey, TValue))) = New Dictionary(Of TKey, LinkedListNode(Of KeyValuePair(Of TKey, TValue)))() Private ReadOnly list As LinkedList(Of KeyValuePair(Of TKey, TValue)) = New LinkedList(Of KeyValuePair(Of TKey, TValue))() ''' ''' Creates a deep clone of this object. ''' ''' ''' A deep clone of this object. ''' Public Function Clone() As MapField(Of TKey, TValue) Implements IDeepCloneable(Of MapField(Of TKey, TValue)).Clone Dim lClone = New MapField(Of TKey, TValue)() ' Keys are never cloneable. Values might be. If GetType(IDeepCloneable(Of TValue)).IsAssignableFrom(GetType(TValue)) Then For Each pair In list lClone.Add(pair.Key, CType(pair.Value, IDeepCloneable(Of TValue)).Clone()) Next Else ' Nothing is cloneable, so we don't need to worry. lClone.Add(Me) End If Return lClone End Function ''' ''' Adds the specified key/value pair to the map. ''' ''' ''' This operation fails if the key already exists in the map. To replace an existing entry, use the indexer. ''' ''' The key to add ''' The value to add. ''' The given key already exists in map. Public Sub Add(key As TKey, value As TValue) Implements IDictionary(Of TKey, TValue).Add ' Validation of arguments happens in ContainsKey and the indexer If ContainsKey(key) Then Throw New ArgumentException("Key already exists in map", NameOf(key)) End If Me(key) = value End Sub ''' ''' Determines whether the specified key is present in the map. ''' ''' The key to check. ''' true if the map contains the given key; false otherwise. Public Function ContainsKey(key As TKey) As Boolean Implements IDictionary(Of TKey, TValue).ContainsKey CheckNotNullUnconstrained(key, NameOf(key)) Return map.ContainsKey(key) End Function Private Function ContainsValue(value As TValue) As Boolean Dim comparer = EqualityComparer(Of TValue).Default Return list.Any(Function(pair) comparer.Equals(pair.Value, value)) End Function ''' ''' Removes the entry identified by the given key from the map. ''' ''' The key indicating the entry to remove from the map. ''' true if the map contained the given key before the entry was removed; false otherwise. Public Function Remove(key As TKey) As Boolean Implements IDictionary(Of TKey, TValue).Remove CheckNotNullUnconstrained(key, NameOf(key)) Dim node As LinkedListNode(Of KeyValuePair(Of TKey, TValue)) If map.TryGetValue(key, node) Then map.Remove(key) node.List.Remove(node) Return True Else Return False End If End Function ''' ''' Gets the value associated with the specified key. ''' ''' The key whose value to get. ''' When this method returns, the value associated with the specified key, if the key is found; ''' otherwise, the default value for the type of the parameter. ''' This parameter is passed uninitialized. ''' true if the map contains an element with the specified key; otherwise, false. Public Function TryGetValue(key As TKey, ByRef value As TValue) As Boolean Implements IDictionary(Of TKey, TValue).TryGetValue Dim node As LinkedListNode(Of KeyValuePair(Of TKey, TValue)) If map.TryGetValue(key, node) Then value = node.Value.Value Return True Else value = Nothing Return False End If End Function ''' ''' Gets or sets the value associated with the specified key. ''' ''' The key of the value to get or set. ''' The property is retrieved and key does not exist in the collection. ''' The value associated with the specified key. If the specified key is not found, ''' a get operation throws a , and a set operation creates a new element with the specified key. Default Public Property Item(key As TKey) As TValue Implements IDictionary(Of TKey, TValue).Item Get CheckNotNullUnconstrained(key, NameOf(key)) Dim value As TValue If TryGetValue(key, value) Then Return value End If Throw New KeyNotFoundException() End Get Set(value As TValue) CheckNotNullUnconstrained(key, NameOf(key)) ' value == null check here is redundant, but avoids boxing. If value Is Nothing Then CheckNotNullUnconstrained(value, NameOf(value)) End If Dim node As LinkedListNode(Of KeyValuePair(Of TKey, TValue)) Dim pair = New KeyValuePair(Of TKey, TValue)(key, value) If map.TryGetValue(key, node) Then node.Value = pair Else node = list.AddLast(pair) map(key) = node End If End Set End Property ''' ''' Gets a collection containing the keys in the map. ''' Public ReadOnly Property KeysProp As ICollection(Of TKey) Implements IDictionary(Of TKey, TValue).Keys Get Return New MapView(Of TKey)(Me, Function(pair) pair.Key, AddressOf ContainsKey) End Get End Property ''' ''' Gets a collection containing the values in the map. ''' Public ReadOnly Property ValuesProp As ICollection(Of TValue) Implements IDictionary(Of TKey, TValue).Values Get Return New MapView(Of TValue)(Me, Function(pair) pair.Value, AddressOf ContainsValue) End Get End Property ''' ''' Adds the specified entries to the map. The keys and values are not automatically cloned. ''' ''' The entries to add to the map. Public Sub Add(entries As IDictionary(Of TKey, TValue)) CheckNotNull(entries, NameOf(entries)) For Each pair In entries Add(pair.Key, pair.Value) Next End Sub ''' ''' Returns an enumerator that iterates through the collection. ''' ''' ''' An enumerator that can be used to iterate through the collection. ''' Public Function GetEnumerator() As IEnumerator(Of KeyValuePair(Of TKey, TValue)) Implements IEnumerable(Of KeyValuePair(Of TKey, TValue)).GetEnumerator Return list.GetEnumerator() End Function ''' ''' Returns an enumerator that iterates through a collection. ''' ''' ''' An object that can be used to iterate through the collection. ''' Private Function GetEnumerator1() As IEnumerator Implements IEnumerable.GetEnumerator Return GetEnumerator() End Function ''' ''' Adds the specified item to the map. ''' ''' The item to add to the map. Private Sub Add1(item As KeyValuePair(Of TKey, TValue)) Implements ICollection(Of KeyValuePair(Of TKey, TValue)).Add Add(item.Key, item.Value) End Sub ''' ''' Removes all items from the map. ''' Public Sub Clear() Implements ICollection(Of KeyValuePair(Of TKey, TValue)).Clear, IDictionary.Clear list.Clear() map.Clear() End Sub ''' ''' Determines whether map contains an entry equivalent to the given key/value pair. ''' ''' The key/value pair to find. ''' Private Function Contains1(item As KeyValuePair(Of TKey, TValue)) As Boolean Implements ICollection(Of KeyValuePair(Of TKey, TValue)).Contains Dim value As TValue Return TryGetValue(item.Key, value) AndAlso EqualityComparer(Of TValue).Default.Equals(item.Value, value) End Function ''' ''' Copies the key/value pairs in this map to an array. ''' ''' The array to copy the entries into. ''' The index of the array at which to start copying values. Private Sub CopyTo1(array As KeyValuePair(Of TKey, TValue)(), arrayIndex As Integer) Implements ICollection(Of KeyValuePair(Of TKey, TValue)).CopyTo list.CopyTo(array, arrayIndex) End Sub ''' ''' Removes the specified key/value pair from the map. ''' ''' Both the key and the value must be found for the entry to be removed. ''' The key/value pair to remove. ''' true if the key/value pair was found and removed; false otherwise. Private Function Remove1(item As KeyValuePair(Of TKey, TValue)) As Boolean Implements ICollection(Of KeyValuePair(Of TKey, TValue)).Remove If item.Key Is Nothing Then Throw New ArgumentException("Key is null", NameOf(item)) End If Dim node As LinkedListNode(Of KeyValuePair(Of TKey, TValue)) If map.TryGetValue(item.Key, node) AndAlso EqualityComparer(Of TValue).Default.Equals(item.Value, node.Value.Value) Then map.Remove(item.Key) node.List.Remove(node) Return True Else Return False End If End Function ''' ''' Gets the number of elements contained in the map. ''' Public ReadOnly Property Count As Integer Implements ICollection(Of KeyValuePair(Of TKey, TValue)).Count, ICollection.Count Get Return list.Count End Get End Property ''' ''' Gets a value indicating whether the map is read-only. ''' Public ReadOnly Property IsReadOnly As Boolean Implements ICollection(Of KeyValuePair(Of TKey, TValue)).IsReadOnly, IDictionary.IsReadOnly Get Return False End Get End Property ''' ''' Determines whether the specified , is equal to this instance. ''' ''' The to compare with this instance. ''' ''' true if the specified is equal to this instance; otherwise, false. ''' Public Overrides Function Equals(other As Object) As Boolean Return Equals(TryCast(other, MapField(Of TKey, TValue))) End Function ''' ''' Returns a hash code for this instance. ''' ''' ''' A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. ''' Public Overrides Function GetHashCode() As Integer Dim valueComparer = EqualityComparer(Of TValue).Default Dim hash = 0 For Each pair In list hash = hash Xor pair.Key.GetHashCode() * 31 + valueComparer.GetHashCode(pair.Value) Next Return hash End Function ''' ''' Compares this map with another for equality. ''' ''' ''' The order of the key/value pairs in the maps is not deemed significant in this comparison. ''' ''' The map to compare this with. ''' true if refers to an equal map; false otherwise. Public Overloads Function Equals(other As MapField(Of TKey, TValue)) As Boolean Implements IEquatable(Of MapField(Of TKey, TValue)).Equals If other Is Nothing Then Return False End If If other Is Me Then Return True End If If other.Count <> Count Then Return False End If Dim valueComparer = EqualityComparer(Of TValue).Default For Each pair In Me Dim value As TValue If Not other.TryGetValue(pair.Key, value) Then Return False End If If Not valueComparer.Equals(value, pair.Value) Then Return False End If Next Return True End Function ''' ''' Adds entries to the map from the given stream. ''' ''' ''' It is assumed that the stream is initially positioned after the tag specified by the codec. ''' This method will continue reading entries from the stream until the end is reached, or ''' a different tag is encountered. ''' ''' Stream to read from ''' Codec describing how the key/value pairs are encoded Public Sub AddEntriesFrom(input As CodedInputStream, codec As Codec) Dim adapter = New Codec.MessageAdapter(codec) Do adapter.Reset() input.ReadMessage(adapter) Me(adapter.Key) = adapter.Value Loop While input.MaybeConsumeTag(codec.MapTag) End Sub ''' ''' Writes the contents of this map to the given coded output stream, using the specified codec ''' to encode each entry. ''' ''' The output stream to write to. ''' The codec to use for each entry. Public Sub WriteTo(output As CodedOutputStream, codec As Codec) Dim message = New Codec.MessageAdapter(codec) For Each entry In list message.Key = entry.Key message.Value = entry.Value output.WriteTag(codec.MapTag) output.WriteMessage(message) Next End Sub ''' ''' Calculates the size of this map based on the given entry codec. ''' ''' The codec to use to encode each entry. ''' Public Function CalculateSize(codec As Codec) As Integer If Count = 0 Then Return 0 End If Dim message = New Codec.MessageAdapter(codec) Dim size = 0 For Each entry In list message.Key = entry.Key message.Value = entry.Value size += CodedOutputStream.ComputeRawVarint32Size(codec.MapTag) size += CodedOutputStream.ComputeMessageSize(message) Next Return size End Function ''' ''' Returns a string representation of this repeated field, in the same ''' way as it would be represented by the default JSON formatter. ''' Public Overrides Function ToString() As String Dim writer = New StringWriter() JsonFormatter.Default.WriteDictionary(writer, Me) Return writer.ToString() End Function #Region "IDictionary explicit interface implementation" Private Sub Add2(key As Object, value As Object) Implements IDictionary.Add Add(key, value) End Sub Private Function Contains2(key As Object) As Boolean Implements IDictionary.Contains If Not (TypeOf key Is TKey) Then Return False End If Return ContainsKey(key) End Function Private Function GetEnumerator2() As IDictionaryEnumerator Implements IDictionary.GetEnumerator Return New DictionaryEnumerator(GetEnumerator()) End Function Private Sub Remove2(key As Object) Implements IDictionary.Remove CheckNotNull(key, NameOf(key)) If Not (TypeOf key Is TKey) Then Return End If Remove(key) End Sub Private Sub CopyTo2(array As Array, index As Integer) Implements ICollection.CopyTo ' This is ugly and slow as heck, but with any luck it will never be used anyway. Dim temp As ICollection = [Select](Function(pair) New DictionaryEntry(pair.Key, pair.Value)).ToList() temp.CopyTo(array, index) End Sub Private ReadOnly Property IsFixedSize As Boolean Implements IDictionary.IsFixedSize Get Return False End Get End Property Private ReadOnly Property Keys As ICollection Implements IDictionary.Keys Get Return CType(KeysProp, ICollection) End Get End Property Private ReadOnly Property Values As ICollection Implements IDictionary.Values Get Return CType(ValuesProp, ICollection) End Get End Property 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(key As Object) As Object Implements IDictionary.Item Get CheckNotNull(key, NameOf(key)) If Not (TypeOf key Is TKey) Then Return Nothing End If Dim value As TValue TryGetValue(key, value) Return value End Get Set(value As Object) Me(key) = CType(value, TValue) End Set End Property #End Region Private Class DictionaryEnumerator Implements IDictionaryEnumerator Private ReadOnly enumerator As IEnumerator(Of KeyValuePair(Of TKey, TValue)) Friend Sub New(enumerator As IEnumerator(Of KeyValuePair(Of TKey, TValue))) Me.enumerator = enumerator End Sub Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext Return enumerator.MoveNext() End Function Public Sub Reset() Implements IEnumerator.Reset enumerator.Reset() End Sub Public ReadOnly Property Current As Object Implements IEnumerator.Current Get Return Entry End Get End Property Public ReadOnly Property Entry As DictionaryEntry Implements IDictionaryEnumerator.Entry Get Return New DictionaryEntry(Key, Value) End Get End Property Public ReadOnly Property Key As Object Implements IDictionaryEnumerator.Key Get Return enumerator.Current.Key End Get End Property Public ReadOnly Property Value As Object Implements IDictionaryEnumerator.Value Get Return enumerator.Current.Value End Get End Property End Class ''' ''' A codec for a specific map field. This contains all the information required to encode and ''' decode the nested messages. ''' Public NotInheritable Class Codec Private ReadOnly keyCodec As FieldCodecType(Of TKey) Private ReadOnly valueCodec As FieldCodecType(Of TValue) Private ReadOnly mapTagField As UInteger ''' ''' Creates a new entry codec based on a separate key codec and value codec, ''' and the tag to use for each map entry. ''' ''' The key codec. ''' The value codec. ''' The map tag to use to introduce each map entry. Public Sub New(keyCodec As FieldCodecType(Of TKey), valueCodec As FieldCodecType(Of TValue), mapTag As UInteger) Me.keyCodec = keyCodec Me.valueCodec = valueCodec mapTagField = mapTag End Sub ''' ''' The tag used in the enclosing message to indicate map entries. ''' Friend ReadOnly Property MapTag As UInteger Get Return mapTagField End Get End Property ''' ''' A mutable message class, used for parsing and serializing. This ''' delegates the work to a codec, but implements the interface ''' for interop with and . ''' This is nested inside Codec as it's tightly coupled to the associated codec, ''' and it's simpler if it has direct access to all its fields. ''' Friend Class MessageAdapter Implements IMessage Private Shared ReadOnly ZeroLengthMessageStreamData As Byte() = New Byte() {0} Private ReadOnly codec As Codec Friend Property Key As TKey Friend Property Value As TValue Friend Sub New(codec As Codec) Me.codec = codec End Sub Friend Sub Reset() Key = codec.keyCodec.DefaultValue Value = codec.valueCodec.DefaultValue End Sub Public Sub MergeFrom(input As CodedInputStream) Implements IMessage.MergeFrom Dim tag As New Value(Of UInteger) While ((tag = input.ReadTag())) <> 0 If tag.Value = codec.keyCodec.Tag Then Key = codec.keyCodec.Read(input) ElseIf tag.Value = codec.valueCodec.Tag Then Value = codec.valueCodec.Read(input) Else input.SkipLastField() End If End While ' Corner case: a map entry with a key but no value, where the value type is a message. ' Read it as if we'd seen an input stream with no data (i.e. create a "default" message). If Value Is Nothing Then Value = codec.valueCodec.Read(New CodedInputStream(ZeroLengthMessageStreamData)) End If End Sub Public Sub WriteTo(output As CodedOutputStream) Implements IMessage.WriteTo codec.keyCodec.WriteTagAndValue(output, Key) codec.valueCodec.WriteTagAndValue(output, Value) End Sub Public Function CalculateSize() As Integer Implements IMessage.CalculateSize Return codec.keyCodec.CalculateSizeWithTag(Key) + codec.valueCodec.CalculateSizeWithTag(Value) End Function Private ReadOnly Property Descriptor As MessageDescriptor Implements IMessage.Descriptor Get Return Nothing End Get End Property End Class End Class Private Class MapView(Of T) Implements ICollection(Of T), ICollection Private ReadOnly parent As MapField(Of TKey, TValue) Private ReadOnly projection As Func(Of KeyValuePair(Of TKey, TValue), T) Private ReadOnly containsCheck As Func(Of T, Boolean) Friend Sub New(parent As MapField(Of TKey, TValue), projection As Func(Of KeyValuePair(Of TKey, TValue), T), containsCheck As Func(Of T, Boolean)) Me.parent = parent Me.projection = projection Me.containsCheck = containsCheck End Sub Public ReadOnly Property Count As Integer Implements ICollection(Of T).Count, ICollection.Count Get Return parent.Count End Get End Property Public ReadOnly Property IsReadOnly As Boolean Implements ICollection(Of T).IsReadOnly Get Return True End Get End Property Public ReadOnly Property IsSynchronized As Boolean Implements ICollection.IsSynchronized Get Return False End Get End Property Public ReadOnly Property SyncRoot As Object Implements ICollection.SyncRoot Get Return parent End Get End Property Public Sub Add(item As T) Implements ICollection(Of T).Add Throw New NotSupportedException() End Sub Public Sub Clear() Implements ICollection(Of T).Clear Throw New NotSupportedException() End Sub Public Function Contains(item As T) As Boolean Implements ICollection(Of T).Contains Return containsCheck(item) End Function Public Sub CopyTo(array As T(), arrayIndex As Integer) Implements ICollection(Of T).CopyTo If arrayIndex < 0 Then Throw New ArgumentOutOfRangeException(NameOf(arrayIndex)) End If If arrayIndex + Count >= array.Length Then Throw New ArgumentException("Not enough space in the array", NameOf(array)) End If For Each item As T In Me array(Math.Min(Threading.Interlocked.Increment(arrayIndex), arrayIndex - 1)) = item Next End Sub Public Function GetEnumerator() As IEnumerator(Of T) Implements IEnumerable(Of T).GetEnumerator Return Enumerable.Select(parent.list, projection).GetEnumerator() End Function Public Function Remove(item As T) As Boolean Implements ICollection(Of T).Remove Throw New NotSupportedException() End Function Private Function GetEnumerator3() As IEnumerator Implements IEnumerable.GetEnumerator Return GetEnumerator() End Function Public Sub CopyTo(array As Array, index As Integer) Implements ICollection.CopyTo If index < 0 Then Throw New ArgumentOutOfRangeException(NameOf(index)) End If If index + Count >= array.Length Then Throw New ArgumentException("Not enough space in the array", NameOf(array)) End If For Each item As T In Me array.SetValue(item, Math.Min(Threading.Interlocked.Increment(index), index - 1)) Next End Sub End Class End Class End Namespace