1 #region Copyright notice and license 2 // Protocol Buffers - Google's data interchange format 3 // Copyright 2015 Google Inc. All rights reserved. 4 // https://developers.google.com/protocol-buffers/ 5 // 6 // Redistribution and use in source and binary forms, with or without 7 // modification, are permitted provided that the following conditions are 8 // met: 9 // 10 // * Redistributions of source code must retain the above copyright 11 // notice, this list of conditions and the following disclaimer. 12 // * Redistributions in binary form must reproduce the above 13 // copyright notice, this list of conditions and the following disclaimer 14 // in the documentation and/or other materials provided with the 15 // distribution. 16 // * Neither the name of Google Inc. nor the names of its 17 // contributors may be used to endorse or promote products derived from 18 // this software without specific prior written permission. 19 // 20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 #endregion 32 33 using Google.Protobuf.Compatibility; 34 using Google.Protobuf.Reflection; 35 using System; 36 using System.Buffers; 37 using System.Collections; 38 using System.Collections.Generic; 39 using System.IO; 40 using System.Linq; 41 using System.Security; 42 43 namespace Google.Protobuf.Collections 44 { 45 /// <summary> 46 /// Representation of a map field in a Protocol Buffer message. 47 /// </summary> 48 /// <typeparam name="TKey">Key type in the map. Must be a type supported by Protocol Buffer map keys.</typeparam> 49 /// <typeparam name="TValue">Value type in the map. Must be a type supported by Protocol Buffers.</typeparam> 50 /// <remarks> 51 /// <para> 52 /// For string keys, the equality comparison is provided by <see cref="StringComparer.Ordinal" />. 53 /// </para> 54 /// <para> 55 /// Null values are not permitted in the map, either for wrapper types or regular messages. 56 /// If a map is deserialized from a data stream and the value is missing from an entry, a default value 57 /// is created instead. For primitive types, that is the regular default value (0, the empty string and so 58 /// on); for message types, an empty instance of the message is created, as if the map entry contained a 0-length 59 /// encoded value for the field. 60 /// </para> 61 /// <para> 62 /// This implementation does not generally prohibit the use of key/value types which are not 63 /// supported by Protocol Buffers (e.g. using a key type of <code>byte</code>) but nor does it guarantee 64 /// that all operations will work in such cases. 65 /// </para> 66 /// <para> 67 /// The order in which entries are returned when iterating over this object is undefined, and may change 68 /// in future versions. 69 /// </para> 70 /// </remarks> 71 public sealed class MapField<TKey, TValue> : IDeepCloneable<MapField<TKey, TValue>>, IDictionary<TKey, TValue>, IEquatable<MapField<TKey, TValue>>, IDictionary 72 #if !NET35 73 , IReadOnlyDictionary<TKey, TValue> 74 #endif 75 { 76 private static readonly EqualityComparer<TValue> ValueEqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<TValue>(); 77 private static readonly EqualityComparer<TKey> KeyEqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<TKey>(); 78 79 // TODO: Don't create the map/list until we have an entry. (Assume many maps will be empty.) 80 private readonly Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> map = 81 new Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>(KeyEqualityComparer); 82 private readonly LinkedList<KeyValuePair<TKey, TValue>> list = new LinkedList<KeyValuePair<TKey, TValue>>(); 83 84 /// <summary> 85 /// Creates a deep clone of this object. 86 /// </summary> 87 /// <returns> 88 /// A deep clone of this object. 89 /// </returns> Clone()90 public MapField<TKey, TValue> Clone() 91 { 92 var clone = new MapField<TKey, TValue>(); 93 // Keys are never cloneable. Values might be. 94 if (typeof(IDeepCloneable<TValue>).IsAssignableFrom(typeof(TValue))) 95 { 96 foreach (var pair in list) 97 { 98 clone.Add(pair.Key, ((IDeepCloneable<TValue>)pair.Value).Clone()); 99 } 100 } 101 else 102 { 103 // Nothing is cloneable, so we don't need to worry. 104 clone.Add(this); 105 } 106 return clone; 107 } 108 109 /// <summary> 110 /// Adds the specified key/value pair to the map. 111 /// </summary> 112 /// <remarks> 113 /// This operation fails if the key already exists in the map. To replace an existing entry, use the indexer. 114 /// </remarks> 115 /// <param name="key">The key to add</param> 116 /// <param name="value">The value to add.</param> 117 /// <exception cref="System.ArgumentException">The given key already exists in map.</exception> Add(TKey key, TValue value)118 public void Add(TKey key, TValue value) 119 { 120 // Validation of arguments happens in ContainsKey and the indexer 121 if (ContainsKey(key)) 122 { 123 throw new ArgumentException("Key already exists in map", nameof(key)); 124 } 125 this[key] = value; 126 } 127 128 /// <summary> 129 /// Determines whether the specified key is present in the map. 130 /// </summary> 131 /// <param name="key">The key to check.</param> 132 /// <returns><c>true</c> if the map contains the given key; <c>false</c> otherwise.</returns> ContainsKey(TKey key)133 public bool ContainsKey(TKey key) 134 { 135 ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key)); 136 return map.ContainsKey(key); 137 } 138 139 private bool ContainsValue(TValue value) => 140 list.Any(pair => ValueEqualityComparer.Equals(pair.Value, value)); 141 142 /// <summary> 143 /// Removes the entry identified by the given key from the map. 144 /// </summary> 145 /// <param name="key">The key indicating the entry to remove from the map.</param> 146 /// <returns><c>true</c> if the map contained the given key before the entry was removed; <c>false</c> otherwise.</returns> Remove(TKey key)147 public bool Remove(TKey key) 148 { 149 ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key)); 150 LinkedListNode<KeyValuePair<TKey, TValue>> node; 151 if (map.TryGetValue(key, out node)) 152 { 153 map.Remove(key); 154 node.List.Remove(node); 155 return true; 156 } 157 else 158 { 159 return false; 160 } 161 } 162 163 /// <summary> 164 /// Gets the value associated with the specified key. 165 /// </summary> 166 /// <param name="key">The key whose value to get.</param> 167 /// <param name="value">When this method returns, the value associated with the specified key, if the key is found; 168 /// otherwise, the default value for the type of the <paramref name="value"/> parameter. 169 /// This parameter is passed uninitialized.</param> 170 /// <returns><c>true</c> if the map contains an element with the specified key; otherwise, <c>false</c>.</returns> TryGetValue(TKey key, out TValue value)171 public bool TryGetValue(TKey key, out TValue value) 172 { 173 LinkedListNode<KeyValuePair<TKey, TValue>> node; 174 if (map.TryGetValue(key, out node)) 175 { 176 value = node.Value.Value; 177 return true; 178 } 179 else 180 { 181 value = default(TValue); 182 return false; 183 } 184 } 185 186 /// <summary> 187 /// Gets or sets the value associated with the specified key. 188 /// </summary> 189 /// <param name="key">The key of the value to get or set.</param> 190 /// <exception cref="KeyNotFoundException">The property is retrieved and key does not exist in the collection.</exception> 191 /// <returns>The value associated with the specified key. If the specified key is not found, 192 /// a get operation throws a <see cref="KeyNotFoundException"/>, and a set operation creates a new element with the specified key.</returns> 193 public TValue this[TKey key] 194 { 195 get 196 { 197 ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key)); 198 TValue value; 199 if (TryGetValue(key, out value)) 200 { 201 return value; 202 } 203 throw new KeyNotFoundException(); 204 } 205 set 206 { 207 ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key)); 208 // value == null check here is redundant, but avoids boxing. 209 if (value == null) 210 { 211 ProtoPreconditions.CheckNotNullUnconstrained(value, nameof(value)); 212 } 213 LinkedListNode<KeyValuePair<TKey, TValue>> node; 214 var pair = new KeyValuePair<TKey, TValue>(key, value); 215 if (map.TryGetValue(key, out node)) 216 { 217 node.Value = pair; 218 } 219 else 220 { 221 node = list.AddLast(pair); 222 map[key] = node; 223 } 224 } 225 } 226 227 /// <summary> 228 /// Gets a collection containing the keys in the map. 229 /// </summary> 230 public ICollection<TKey> Keys { get { return new MapView<TKey>(this, pair => pair.Key, ContainsKey); } } 231 232 /// <summary> 233 /// Gets a collection containing the values in the map. 234 /// </summary> 235 public ICollection<TValue> Values { get { return new MapView<TValue>(this, pair => pair.Value, ContainsValue); } } 236 237 /// <summary> 238 /// Adds the specified entries to the map. The keys and values are not automatically cloned. 239 /// </summary> 240 /// <param name="entries">The entries to add to the map.</param> Add(IDictionary<TKey, TValue> entries)241 public void Add(IDictionary<TKey, TValue> entries) 242 { 243 ProtoPreconditions.CheckNotNull(entries, nameof(entries)); 244 foreach (var pair in entries) 245 { 246 Add(pair.Key, pair.Value); 247 } 248 } 249 250 /// <summary> 251 /// Returns an enumerator that iterates through the collection. 252 /// </summary> 253 /// <returns> 254 /// An enumerator that can be used to iterate through the collection. 255 /// </returns> GetEnumerator()256 public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() 257 { 258 return list.GetEnumerator(); 259 } 260 261 /// <summary> 262 /// Returns an enumerator that iterates through a collection. 263 /// </summary> 264 /// <returns> 265 /// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection. 266 /// </returns> IEnumerable.GetEnumerator()267 IEnumerator IEnumerable.GetEnumerator() 268 { 269 return GetEnumerator(); 270 } 271 272 /// <summary> 273 /// Adds the specified item to the map. 274 /// </summary> 275 /// <param name="item">The item to add to the map.</param> Add(KeyValuePair<TKey, TValue> item)276 void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) 277 { 278 Add(item.Key, item.Value); 279 } 280 281 /// <summary> 282 /// Removes all items from the map. 283 /// </summary> Clear()284 public void Clear() 285 { 286 list.Clear(); 287 map.Clear(); 288 } 289 290 /// <summary> 291 /// Determines whether map contains an entry equivalent to the given key/value pair. 292 /// </summary> 293 /// <param name="item">The key/value pair to find.</param> 294 /// <returns></returns> Contains(KeyValuePair<TKey, TValue> item)295 bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) 296 { 297 TValue value; 298 return TryGetValue(item.Key, out value) && ValueEqualityComparer.Equals(item.Value, value); 299 } 300 301 /// <summary> 302 /// Copies the key/value pairs in this map to an array. 303 /// </summary> 304 /// <param name="array">The array to copy the entries into.</param> 305 /// <param name="arrayIndex">The index of the array at which to start copying values.</param> CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)306 void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) 307 { 308 list.CopyTo(array, arrayIndex); 309 } 310 311 /// <summary> 312 /// Removes the specified key/value pair from the map. 313 /// </summary> 314 /// <remarks>Both the key and the value must be found for the entry to be removed.</remarks> 315 /// <param name="item">The key/value pair to remove.</param> 316 /// <returns><c>true</c> if the key/value pair was found and removed; <c>false</c> otherwise.</returns> Remove(KeyValuePair<TKey, TValue> item)317 bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) 318 { 319 if (item.Key == null) 320 { 321 throw new ArgumentException("Key is null", nameof(item)); 322 } 323 LinkedListNode<KeyValuePair<TKey, TValue>> node; 324 if (map.TryGetValue(item.Key, out node) && 325 EqualityComparer<TValue>.Default.Equals(item.Value, node.Value.Value)) 326 { 327 map.Remove(item.Key); 328 node.List.Remove(node); 329 return true; 330 } 331 else 332 { 333 return false; 334 } 335 } 336 337 /// <summary> 338 /// Gets the number of elements contained in the map. 339 /// </summary> 340 public int Count { get { return list.Count; } } 341 342 /// <summary> 343 /// Gets a value indicating whether the map is read-only. 344 /// </summary> 345 public bool IsReadOnly { get { return false; } } 346 347 /// <summary> 348 /// Determines whether the specified <see cref="System.Object" />, is equal to this instance. 349 /// </summary> 350 /// <param name="other">The <see cref="System.Object" /> to compare with this instance.</param> 351 /// <returns> 352 /// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>. 353 /// </returns> Equals(object other)354 public override bool Equals(object other) 355 { 356 return Equals(other as MapField<TKey, TValue>); 357 } 358 359 /// <summary> 360 /// Returns a hash code for this instance. 361 /// </summary> 362 /// <returns> 363 /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 364 /// </returns> GetHashCode()365 public override int GetHashCode() 366 { 367 var keyComparer = KeyEqualityComparer; 368 var valueComparer = ValueEqualityComparer; 369 int hash = 0; 370 foreach (var pair in list) 371 { 372 hash ^= keyComparer.GetHashCode(pair.Key) * 31 + valueComparer.GetHashCode(pair.Value); 373 } 374 return hash; 375 } 376 377 /// <summary> 378 /// Compares this map with another for equality. 379 /// </summary> 380 /// <remarks> 381 /// The order of the key/value pairs in the maps is not deemed significant in this comparison. 382 /// </remarks> 383 /// <param name="other">The map to compare this with.</param> 384 /// <returns><c>true</c> if <paramref name="other"/> refers to an equal map; <c>false</c> otherwise.</returns> Equals(MapField<TKey, TValue> other)385 public bool Equals(MapField<TKey, TValue> other) 386 { 387 if (other == null) 388 { 389 return false; 390 } 391 if (other == this) 392 { 393 return true; 394 } 395 if (other.Count != this.Count) 396 { 397 return false; 398 } 399 var valueComparer = ValueEqualityComparer; 400 foreach (var pair in this) 401 { 402 TValue value; 403 if (!other.TryGetValue(pair.Key, out value)) 404 { 405 return false; 406 } 407 if (!valueComparer.Equals(value, pair.Value)) 408 { 409 return false; 410 } 411 } 412 return true; 413 } 414 415 /// <summary> 416 /// Adds entries to the map from the given stream. 417 /// </summary> 418 /// <remarks> 419 /// It is assumed that the stream is initially positioned after the tag specified by the codec. 420 /// This method will continue reading entries from the stream until the end is reached, or 421 /// a different tag is encountered. 422 /// </remarks> 423 /// <param name="input">Stream to read from</param> 424 /// <param name="codec">Codec describing how the key/value pairs are encoded</param> AddEntriesFrom(CodedInputStream input, Codec codec)425 public void AddEntriesFrom(CodedInputStream input, Codec codec) 426 { 427 ParseContext.Initialize(input, out ParseContext ctx); 428 try 429 { 430 AddEntriesFrom(ref ctx, codec); 431 } 432 finally 433 { 434 ctx.CopyStateTo(input); 435 } 436 } 437 438 /// <summary> 439 /// Adds entries to the map from the given parse context. 440 /// </summary> 441 /// <remarks> 442 /// It is assumed that the input is initially positioned after the tag specified by the codec. 443 /// This method will continue reading entries from the input until the end is reached, or 444 /// a different tag is encountered. 445 /// </remarks> 446 /// <param name="ctx">Input to read from</param> 447 /// <param name="codec">Codec describing how the key/value pairs are encoded</param> 448 [SecuritySafeCritical] AddEntriesFrom(ref ParseContext ctx, Codec codec)449 public void AddEntriesFrom(ref ParseContext ctx, Codec codec) 450 { 451 var adapter = new Codec.MessageAdapter(codec); 452 do 453 { 454 adapter.Reset(); 455 ctx.ReadMessage(adapter); 456 this[adapter.Key] = adapter.Value; 457 } while (ParsingPrimitives.MaybeConsumeTag(ref ctx.buffer, ref ctx.state, codec.MapTag)); 458 } 459 460 /// <summary> 461 /// Writes the contents of this map to the given coded output stream, using the specified codec 462 /// to encode each entry. 463 /// </summary> 464 /// <param name="output">The output stream to write to.</param> 465 /// <param name="codec">The codec to use for each entry.</param> WriteTo(CodedOutputStream output, Codec codec)466 public void WriteTo(CodedOutputStream output, Codec codec) 467 { 468 WriteContext.Initialize(output, out WriteContext ctx); 469 try 470 { 471 WriteTo(ref ctx, codec); 472 } 473 finally 474 { 475 ctx.CopyStateTo(output); 476 } 477 } 478 479 /// <summary> 480 /// Writes the contents of this map to the given write context, using the specified codec 481 /// to encode each entry. 482 /// </summary> 483 /// <param name="ctx">The write context to write to.</param> 484 /// <param name="codec">The codec to use for each entry.</param> 485 [SecuritySafeCritical] WriteTo(ref WriteContext ctx, Codec codec)486 public void WriteTo(ref WriteContext ctx, Codec codec) 487 { 488 var message = new Codec.MessageAdapter(codec); 489 foreach (var entry in list) 490 { 491 message.Key = entry.Key; 492 message.Value = entry.Value; 493 ctx.WriteTag(codec.MapTag); 494 ctx.WriteMessage(message); 495 } 496 } 497 498 /// <summary> 499 /// Calculates the size of this map based on the given entry codec. 500 /// </summary> 501 /// <param name="codec">The codec to use to encode each entry.</param> 502 /// <returns></returns> CalculateSize(Codec codec)503 public int CalculateSize(Codec codec) 504 { 505 if (Count == 0) 506 { 507 return 0; 508 } 509 var message = new Codec.MessageAdapter(codec); 510 int size = 0; 511 foreach (var entry in list) 512 { 513 message.Key = entry.Key; 514 message.Value = entry.Value; 515 size += CodedOutputStream.ComputeRawVarint32Size(codec.MapTag); 516 size += CodedOutputStream.ComputeMessageSize(message); 517 } 518 return size; 519 } 520 521 /// <summary> 522 /// Returns a string representation of this repeated field, in the same 523 /// way as it would be represented by the default JSON formatter. 524 /// </summary> ToString()525 public override string ToString() 526 { 527 var writer = new StringWriter(); 528 JsonFormatter.Default.WriteDictionary(writer, this); 529 return writer.ToString(); 530 } 531 532 #region IDictionary explicit interface implementation IDictionary.Add(object key, object value)533 void IDictionary.Add(object key, object value) 534 { 535 Add((TKey)key, (TValue)value); 536 } 537 IDictionary.Contains(object key)538 bool IDictionary.Contains(object key) 539 { 540 if (!(key is TKey)) 541 { 542 return false; 543 } 544 return ContainsKey((TKey)key); 545 } 546 IDictionary.GetEnumerator()547 IDictionaryEnumerator IDictionary.GetEnumerator() 548 { 549 return new DictionaryEnumerator(GetEnumerator()); 550 } 551 IDictionary.Remove(object key)552 void IDictionary.Remove(object key) 553 { 554 ProtoPreconditions.CheckNotNull(key, nameof(key)); 555 if (!(key is TKey)) 556 { 557 return; 558 } 559 Remove((TKey)key); 560 } 561 ICollection.CopyTo(Array array, int index)562 void ICollection.CopyTo(Array array, int index) 563 { 564 // This is ugly and slow as heck, but with any luck it will never be used anyway. 565 ICollection temp = this.Select(pair => new DictionaryEntry(pair.Key, pair.Value)).ToList(); 566 temp.CopyTo(array, index); 567 } 568 569 bool IDictionary.IsFixedSize { get { return false; } } 570 571 ICollection IDictionary.Keys { get { return (ICollection)Keys; } } 572 573 ICollection IDictionary.Values { get { return (ICollection)Values; } } 574 575 bool ICollection.IsSynchronized { get { return false; } } 576 577 object ICollection.SyncRoot { get { return this; } } 578 579 object IDictionary.this[object key] 580 { 581 get 582 { 583 ProtoPreconditions.CheckNotNull(key, nameof(key)); 584 if (!(key is TKey)) 585 { 586 return null; 587 } 588 TValue value; 589 TryGetValue((TKey)key, out value); 590 return value; 591 } 592 593 set 594 { 595 this[(TKey)key] = (TValue)value; 596 } 597 } 598 #endregion 599 600 #region IReadOnlyDictionary explicit interface implementation 601 #if !NET35 602 IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => Keys; 603 604 IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values; 605 #endif 606 #endregion 607 608 private class DictionaryEnumerator : IDictionaryEnumerator 609 { 610 private readonly IEnumerator<KeyValuePair<TKey, TValue>> enumerator; 611 DictionaryEnumerator(IEnumerator<KeyValuePair<TKey, TValue>> enumerator)612 internal DictionaryEnumerator(IEnumerator<KeyValuePair<TKey, TValue>> enumerator) 613 { 614 this.enumerator = enumerator; 615 } 616 MoveNext()617 public bool MoveNext() 618 { 619 return enumerator.MoveNext(); 620 } 621 Reset()622 public void Reset() 623 { 624 enumerator.Reset(); 625 } 626 627 public object Current { get { return Entry; } } 628 public DictionaryEntry Entry { get { return new DictionaryEntry(Key, Value); } } 629 public object Key { get { return enumerator.Current.Key; } } 630 public object Value { get { return enumerator.Current.Value; } } 631 } 632 633 /// <summary> 634 /// A codec for a specific map field. This contains all the information required to encode and 635 /// decode the nested messages. 636 /// </summary> 637 public sealed class Codec 638 { 639 private readonly FieldCodec<TKey> keyCodec; 640 private readonly FieldCodec<TValue> valueCodec; 641 private readonly uint mapTag; 642 643 /// <summary> 644 /// Creates a new entry codec based on a separate key codec and value codec, 645 /// and the tag to use for each map entry. 646 /// </summary> 647 /// <param name="keyCodec">The key codec.</param> 648 /// <param name="valueCodec">The value codec.</param> 649 /// <param name="mapTag">The map tag to use to introduce each map entry.</param> Codec(FieldCodec<TKey> keyCodec, FieldCodec<TValue> valueCodec, uint mapTag)650 public Codec(FieldCodec<TKey> keyCodec, FieldCodec<TValue> valueCodec, uint mapTag) 651 { 652 this.keyCodec = keyCodec; 653 this.valueCodec = valueCodec; 654 this.mapTag = mapTag; 655 } 656 657 /// <summary> 658 /// The tag used in the enclosing message to indicate map entries. 659 /// </summary> 660 internal uint MapTag { get { return mapTag; } } 661 662 /// <summary> 663 /// A mutable message class, used for parsing and serializing. This 664 /// delegates the work to a codec, but implements the <see cref="IMessage"/> interface 665 /// for interop with <see cref="CodedInputStream"/> and <see cref="CodedOutputStream"/>. 666 /// This is nested inside Codec as it's tightly coupled to the associated codec, 667 /// and it's simpler if it has direct access to all its fields. 668 /// </summary> 669 internal class MessageAdapter : IMessage, IBufferMessage 670 { 671 private static readonly byte[] ZeroLengthMessageStreamData = new byte[] { 0 }; 672 673 private readonly Codec codec; 674 internal TKey Key { get; set; } 675 internal TValue Value { get; set; } 676 MessageAdapter(Codec codec)677 internal MessageAdapter(Codec codec) 678 { 679 this.codec = codec; 680 } 681 Reset()682 internal void Reset() 683 { 684 Key = codec.keyCodec.DefaultValue; 685 Value = codec.valueCodec.DefaultValue; 686 } 687 MergeFrom(CodedInputStream input)688 public void MergeFrom(CodedInputStream input) 689 { 690 // Message adapter is an internal class and we know that all the parsing will happen via InternalMergeFrom. 691 throw new NotImplementedException(); 692 } 693 694 [SecuritySafeCritical] InternalMergeFrom(ref ParseContext ctx)695 public void InternalMergeFrom(ref ParseContext ctx) 696 { 697 uint tag; 698 while ((tag = ctx.ReadTag()) != 0) 699 { 700 if (tag == codec.keyCodec.Tag) 701 { 702 Key = codec.keyCodec.Read(ref ctx); 703 } 704 else if (tag == codec.valueCodec.Tag) 705 { 706 Value = codec.valueCodec.Read(ref ctx); 707 } 708 else 709 { 710 ParsingPrimitivesMessages.SkipLastField(ref ctx.buffer, ref ctx.state); 711 } 712 } 713 714 // Corner case: a map entry with a key but no value, where the value type is a message. 715 // Read it as if we'd seen input with no data (i.e. create a "default" message). 716 if (Value == null) 717 { 718 if (ctx.state.CodedInputStream != null) 719 { 720 // the decoded message might not support parsing from ParseContext, so 721 // we need to allow fallback to the legacy MergeFrom(CodedInputStream) parsing. 722 Value = codec.valueCodec.Read(new CodedInputStream(ZeroLengthMessageStreamData)); 723 } 724 else 725 { 726 ParseContext.Initialize(new ReadOnlySequence<byte>(ZeroLengthMessageStreamData), out ParseContext zeroLengthCtx); 727 Value = codec.valueCodec.Read(ref zeroLengthCtx); 728 } 729 } 730 } 731 WriteTo(CodedOutputStream output)732 public void WriteTo(CodedOutputStream output) 733 { 734 // Message adapter is an internal class and we know that all the writing will happen via InternalWriteTo. 735 throw new NotImplementedException(); 736 } 737 738 [SecuritySafeCritical] InternalWriteTo(ref WriteContext ctx)739 public void InternalWriteTo(ref WriteContext ctx) 740 { 741 codec.keyCodec.WriteTagAndValue(ref ctx, Key); 742 codec.valueCodec.WriteTagAndValue(ref ctx, Value); 743 } 744 CalculateSize()745 public int CalculateSize() 746 { 747 return codec.keyCodec.CalculateSizeWithTag(Key) + codec.valueCodec.CalculateSizeWithTag(Value); 748 } 749 750 MessageDescriptor IMessage.Descriptor { get { return null; } } 751 } 752 } 753 754 private class MapView<T> : ICollection<T>, ICollection 755 { 756 private readonly MapField<TKey, TValue> parent; 757 private readonly Func<KeyValuePair<TKey, TValue>, T> projection; 758 private readonly Func<T, bool> containsCheck; 759 MapView( MapField<TKey, TValue> parent, Func<KeyValuePair<TKey, TValue>, T> projection, Func<T, bool> containsCheck)760 internal MapView( 761 MapField<TKey, TValue> parent, 762 Func<KeyValuePair<TKey, TValue>, T> projection, 763 Func<T, bool> containsCheck) 764 { 765 this.parent = parent; 766 this.projection = projection; 767 this.containsCheck = containsCheck; 768 } 769 770 public int Count { get { return parent.Count; } } 771 772 public bool IsReadOnly { get { return true; } } 773 774 public bool IsSynchronized { get { return false; } } 775 776 public object SyncRoot { get { return parent; } } 777 Add(T item)778 public void Add(T item) 779 { 780 throw new NotSupportedException(); 781 } 782 Clear()783 public void Clear() 784 { 785 throw new NotSupportedException(); 786 } 787 Contains(T item)788 public bool Contains(T item) 789 { 790 return containsCheck(item); 791 } 792 CopyTo(T[] array, int arrayIndex)793 public void CopyTo(T[] array, int arrayIndex) 794 { 795 if (arrayIndex < 0) 796 { 797 throw new ArgumentOutOfRangeException(nameof(arrayIndex)); 798 } 799 if (arrayIndex + Count > array.Length) 800 { 801 throw new ArgumentException("Not enough space in the array", nameof(array)); 802 } 803 foreach (var item in this) 804 { 805 array[arrayIndex++] = item; 806 } 807 } 808 GetEnumerator()809 public IEnumerator<T> GetEnumerator() 810 { 811 return parent.list.Select(projection).GetEnumerator(); 812 } 813 Remove(T item)814 public bool Remove(T item) 815 { 816 throw new NotSupportedException(); 817 } 818 IEnumerable.GetEnumerator()819 IEnumerator IEnumerable.GetEnumerator() 820 { 821 return GetEnumerator(); 822 } 823 CopyTo(Array array, int index)824 public void CopyTo(Array array, int index) 825 { 826 if (index < 0) 827 { 828 throw new ArgumentOutOfRangeException(nameof(index)); 829 } 830 if (index + Count > array.Length) 831 { 832 throw new ArgumentException("Not enough space in the array", nameof(array)); 833 } 834 foreach (var item in this) 835 { 836 array.SetValue(item, index++); 837 } 838 } 839 } 840 } 841 } 842