blob: dbbcc14873679bc619c543e7a435c2e6ce119b6d [file] [log] [blame]
Brian Silverman9c614bc2016-02-15 20:20:02 -05001#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
Austin Schuh40c16522018-10-28 20:27:54 -070033using Google.Protobuf.Compatibility;
Brian Silverman9c614bc2016-02-15 20:20:02 -050034using Google.Protobuf.Reflection;
35using System;
36using System.Collections;
37using System.Collections.Generic;
Austin Schuh40c16522018-10-28 20:27:54 -070038using System.IO;
Brian Silverman9c614bc2016-02-15 20:20:02 -050039using System.Linq;
Brian Silverman9c614bc2016-02-15 20:20:02 -050040
41namespace Google.Protobuf.Collections
42{
43 /// <summary>
44 /// Representation of a map field in a Protocol Buffer message.
45 /// </summary>
46 /// <typeparam name="TKey">Key type in the map. Must be a type supported by Protocol Buffer map keys.</typeparam>
47 /// <typeparam name="TValue">Value type in the map. Must be a type supported by Protocol Buffers.</typeparam>
48 /// <remarks>
49 /// <para>
Brian Silverman9c614bc2016-02-15 20:20:02 -050050 /// For string keys, the equality comparison is provided by <see cref="StringComparer.Ordinal" />.
51 /// </para>
52 /// <para>
53 /// Null values are not permitted in the map, either for wrapper types or regular messages.
54 /// If a map is deserialized from a data stream and the value is missing from an entry, a default value
55 /// is created instead. For primitive types, that is the regular default value (0, the empty string and so
56 /// on); for message types, an empty instance of the message is created, as if the map entry contained a 0-length
57 /// encoded value for the field.
58 /// </para>
59 /// <para>
60 /// This implementation does not generally prohibit the use of key/value types which are not
61 /// supported by Protocol Buffers (e.g. using a key type of <code>byte</code>) but nor does it guarantee
62 /// that all operations will work in such cases.
63 /// </para>
Austin Schuh40c16522018-10-28 20:27:54 -070064 /// <para>
65 /// The order in which entries are returned when iterating over this object is undefined, and may change
66 /// in future versions.
67 /// </para>
Brian Silverman9c614bc2016-02-15 20:20:02 -050068 /// </remarks>
69 public sealed class MapField<TKey, TValue> : IDeepCloneable<MapField<TKey, TValue>>, IDictionary<TKey, TValue>, IEquatable<MapField<TKey, TValue>>, IDictionary
Austin Schuh40c16522018-10-28 20:27:54 -070070#if !NET35
71 , IReadOnlyDictionary<TKey, TValue>
72#endif
Brian Silverman9c614bc2016-02-15 20:20:02 -050073 {
Austin Schuh40c16522018-10-28 20:27:54 -070074 private static readonly EqualityComparer<TValue> ValueEqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<TValue>();
75 private static readonly EqualityComparer<TKey> KeyEqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<TKey>();
76
Brian Silverman9c614bc2016-02-15 20:20:02 -050077 // TODO: Don't create the map/list until we have an entry. (Assume many maps will be empty.)
78 private readonly Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> map =
Austin Schuh40c16522018-10-28 20:27:54 -070079 new Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>(KeyEqualityComparer);
Brian Silverman9c614bc2016-02-15 20:20:02 -050080 private readonly LinkedList<KeyValuePair<TKey, TValue>> list = new LinkedList<KeyValuePair<TKey, TValue>>();
81
82 /// <summary>
83 /// Creates a deep clone of this object.
84 /// </summary>
85 /// <returns>
86 /// A deep clone of this object.
87 /// </returns>
88 public MapField<TKey, TValue> Clone()
89 {
90 var clone = new MapField<TKey, TValue>();
91 // Keys are never cloneable. Values might be.
92 if (typeof(IDeepCloneable<TValue>).IsAssignableFrom(typeof(TValue)))
93 {
94 foreach (var pair in list)
95 {
96 clone.Add(pair.Key, ((IDeepCloneable<TValue>)pair.Value).Clone());
97 }
98 }
99 else
100 {
101 // Nothing is cloneable, so we don't need to worry.
102 clone.Add(this);
103 }
104 return clone;
105 }
106
107 /// <summary>
108 /// Adds the specified key/value pair to the map.
109 /// </summary>
110 /// <remarks>
111 /// This operation fails if the key already exists in the map. To replace an existing entry, use the indexer.
112 /// </remarks>
113 /// <param name="key">The key to add</param>
114 /// <param name="value">The value to add.</param>
115 /// <exception cref="System.ArgumentException">The given key already exists in map.</exception>
116 public void Add(TKey key, TValue value)
117 {
118 // Validation of arguments happens in ContainsKey and the indexer
119 if (ContainsKey(key))
120 {
Austin Schuh40c16522018-10-28 20:27:54 -0700121 throw new ArgumentException("Key already exists in map", nameof(key));
Brian Silverman9c614bc2016-02-15 20:20:02 -0500122 }
123 this[key] = value;
124 }
125
126 /// <summary>
127 /// Determines whether the specified key is present in the map.
128 /// </summary>
129 /// <param name="key">The key to check.</param>
130 /// <returns><c>true</c> if the map contains the given key; <c>false</c> otherwise.</returns>
131 public bool ContainsKey(TKey key)
132 {
Austin Schuh40c16522018-10-28 20:27:54 -0700133 ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
Brian Silverman9c614bc2016-02-15 20:20:02 -0500134 return map.ContainsKey(key);
135 }
136
Austin Schuh40c16522018-10-28 20:27:54 -0700137 private bool ContainsValue(TValue value) =>
138 list.Any(pair => ValueEqualityComparer.Equals(pair.Value, value));
Brian Silverman9c614bc2016-02-15 20:20:02 -0500139
140 /// <summary>
141 /// Removes the entry identified by the given key from the map.
142 /// </summary>
143 /// <param name="key">The key indicating the entry to remove from the map.</param>
144 /// <returns><c>true</c> if the map contained the given key before the entry was removed; <c>false</c> otherwise.</returns>
145 public bool Remove(TKey key)
146 {
Austin Schuh40c16522018-10-28 20:27:54 -0700147 ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
Brian Silverman9c614bc2016-02-15 20:20:02 -0500148 LinkedListNode<KeyValuePair<TKey, TValue>> node;
149 if (map.TryGetValue(key, out node))
150 {
151 map.Remove(key);
152 node.List.Remove(node);
153 return true;
154 }
155 else
156 {
157 return false;
158 }
159 }
160
161 /// <summary>
162 /// Gets the value associated with the specified key.
163 /// </summary>
164 /// <param name="key">The key whose value to get.</param>
165 /// <param name="value">When this method returns, the value associated with the specified key, if the key is found;
166 /// otherwise, the default value for the type of the <paramref name="value"/> parameter.
167 /// This parameter is passed uninitialized.</param>
168 /// <returns><c>true</c> if the map contains an element with the specified key; otherwise, <c>false</c>.</returns>
169 public bool TryGetValue(TKey key, out TValue value)
170 {
171 LinkedListNode<KeyValuePair<TKey, TValue>> node;
172 if (map.TryGetValue(key, out node))
173 {
174 value = node.Value.Value;
175 return true;
176 }
177 else
178 {
179 value = default(TValue);
180 return false;
181 }
182 }
183
184 /// <summary>
185 /// Gets or sets the value associated with the specified key.
186 /// </summary>
187 /// <param name="key">The key of the value to get or set.</param>
188 /// <exception cref="KeyNotFoundException">The property is retrieved and key does not exist in the collection.</exception>
189 /// <returns>The value associated with the specified key. If the specified key is not found,
190 /// a get operation throws a <see cref="KeyNotFoundException"/>, and a set operation creates a new element with the specified key.</returns>
191 public TValue this[TKey key]
192 {
193 get
194 {
Austin Schuh40c16522018-10-28 20:27:54 -0700195 ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
Brian Silverman9c614bc2016-02-15 20:20:02 -0500196 TValue value;
197 if (TryGetValue(key, out value))
198 {
199 return value;
200 }
201 throw new KeyNotFoundException();
202 }
203 set
204 {
Austin Schuh40c16522018-10-28 20:27:54 -0700205 ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
Brian Silverman9c614bc2016-02-15 20:20:02 -0500206 // value == null check here is redundant, but avoids boxing.
207 if (value == null)
208 {
Austin Schuh40c16522018-10-28 20:27:54 -0700209 ProtoPreconditions.CheckNotNullUnconstrained(value, nameof(value));
Brian Silverman9c614bc2016-02-15 20:20:02 -0500210 }
211 LinkedListNode<KeyValuePair<TKey, TValue>> node;
212 var pair = new KeyValuePair<TKey, TValue>(key, value);
213 if (map.TryGetValue(key, out node))
214 {
215 node.Value = pair;
216 }
217 else
218 {
219 node = list.AddLast(pair);
220 map[key] = node;
221 }
222 }
223 }
224
225 /// <summary>
226 /// Gets a collection containing the keys in the map.
227 /// </summary>
228 public ICollection<TKey> Keys { get { return new MapView<TKey>(this, pair => pair.Key, ContainsKey); } }
229
230 /// <summary>
231 /// Gets a collection containing the values in the map.
232 /// </summary>
233 public ICollection<TValue> Values { get { return new MapView<TValue>(this, pair => pair.Value, ContainsValue); } }
234
235 /// <summary>
236 /// Adds the specified entries to the map. The keys and values are not automatically cloned.
237 /// </summary>
238 /// <param name="entries">The entries to add to the map.</param>
239 public void Add(IDictionary<TKey, TValue> entries)
240 {
Austin Schuh40c16522018-10-28 20:27:54 -0700241 ProtoPreconditions.CheckNotNull(entries, nameof(entries));
Brian Silverman9c614bc2016-02-15 20:20:02 -0500242 foreach (var pair in entries)
243 {
244 Add(pair.Key, pair.Value);
245 }
246 }
247
248 /// <summary>
249 /// Returns an enumerator that iterates through the collection.
250 /// </summary>
251 /// <returns>
252 /// An enumerator that can be used to iterate through the collection.
253 /// </returns>
254 public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
255 {
256 return list.GetEnumerator();
257 }
258
259 /// <summary>
260 /// Returns an enumerator that iterates through a collection.
261 /// </summary>
262 /// <returns>
263 /// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
264 /// </returns>
265 IEnumerator IEnumerable.GetEnumerator()
266 {
267 return GetEnumerator();
268 }
269
270 /// <summary>
271 /// Adds the specified item to the map.
272 /// </summary>
273 /// <param name="item">The item to add to the map.</param>
274 void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
275 {
276 Add(item.Key, item.Value);
277 }
278
279 /// <summary>
280 /// Removes all items from the map.
281 /// </summary>
282 public void Clear()
283 {
284 list.Clear();
285 map.Clear();
286 }
287
288 /// <summary>
289 /// Determines whether map contains an entry equivalent to the given key/value pair.
290 /// </summary>
291 /// <param name="item">The key/value pair to find.</param>
292 /// <returns></returns>
293 bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
294 {
295 TValue value;
Austin Schuh40c16522018-10-28 20:27:54 -0700296 return TryGetValue(item.Key, out value) && ValueEqualityComparer.Equals(item.Value, value);
Brian Silverman9c614bc2016-02-15 20:20:02 -0500297 }
298
299 /// <summary>
300 /// Copies the key/value pairs in this map to an array.
301 /// </summary>
302 /// <param name="array">The array to copy the entries into.</param>
303 /// <param name="arrayIndex">The index of the array at which to start copying values.</param>
304 void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
305 {
306 list.CopyTo(array, arrayIndex);
307 }
308
309 /// <summary>
310 /// Removes the specified key/value pair from the map.
311 /// </summary>
312 /// <remarks>Both the key and the value must be found for the entry to be removed.</remarks>
313 /// <param name="item">The key/value pair to remove.</param>
314 /// <returns><c>true</c> if the key/value pair was found and removed; <c>false</c> otherwise.</returns>
315 bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
316 {
317 if (item.Key == null)
318 {
Austin Schuh40c16522018-10-28 20:27:54 -0700319 throw new ArgumentException("Key is null", nameof(item));
Brian Silverman9c614bc2016-02-15 20:20:02 -0500320 }
321 LinkedListNode<KeyValuePair<TKey, TValue>> node;
322 if (map.TryGetValue(item.Key, out node) &&
323 EqualityComparer<TValue>.Default.Equals(item.Value, node.Value.Value))
324 {
325 map.Remove(item.Key);
326 node.List.Remove(node);
327 return true;
328 }
329 else
330 {
331 return false;
332 }
333 }
334
335 /// <summary>
336 /// Gets the number of elements contained in the map.
337 /// </summary>
338 public int Count { get { return list.Count; } }
339
340 /// <summary>
341 /// Gets a value indicating whether the map is read-only.
342 /// </summary>
343 public bool IsReadOnly { get { return false; } }
344
345 /// <summary>
346 /// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
347 /// </summary>
348 /// <param name="other">The <see cref="System.Object" /> to compare with this instance.</param>
349 /// <returns>
350 /// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
351 /// </returns>
352 public override bool Equals(object other)
353 {
354 return Equals(other as MapField<TKey, TValue>);
355 }
356
357 /// <summary>
358 /// Returns a hash code for this instance.
359 /// </summary>
360 /// <returns>
361 /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
362 /// </returns>
363 public override int GetHashCode()
364 {
Austin Schuh40c16522018-10-28 20:27:54 -0700365 var keyComparer = KeyEqualityComparer;
366 var valueComparer = ValueEqualityComparer;
Brian Silverman9c614bc2016-02-15 20:20:02 -0500367 int hash = 0;
368 foreach (var pair in list)
369 {
Austin Schuh40c16522018-10-28 20:27:54 -0700370 hash ^= keyComparer.GetHashCode(pair.Key) * 31 + valueComparer.GetHashCode(pair.Value);
Brian Silverman9c614bc2016-02-15 20:20:02 -0500371 }
372 return hash;
373 }
374
375 /// <summary>
376 /// Compares this map with another for equality.
377 /// </summary>
378 /// <remarks>
379 /// The order of the key/value pairs in the maps is not deemed significant in this comparison.
380 /// </remarks>
381 /// <param name="other">The map to compare this with.</param>
382 /// <returns><c>true</c> if <paramref name="other"/> refers to an equal map; <c>false</c> otherwise.</returns>
383 public bool Equals(MapField<TKey, TValue> other)
384 {
385 if (other == null)
386 {
387 return false;
388 }
389 if (other == this)
390 {
391 return true;
392 }
393 if (other.Count != this.Count)
394 {
395 return false;
396 }
Austin Schuh40c16522018-10-28 20:27:54 -0700397 var valueComparer = ValueEqualityComparer;
Brian Silverman9c614bc2016-02-15 20:20:02 -0500398 foreach (var pair in this)
399 {
400 TValue value;
401 if (!other.TryGetValue(pair.Key, out value))
402 {
403 return false;
404 }
405 if (!valueComparer.Equals(value, pair.Value))
406 {
407 return false;
408 }
409 }
410 return true;
411 }
412
413 /// <summary>
414 /// Adds entries to the map from the given stream.
415 /// </summary>
416 /// <remarks>
417 /// It is assumed that the stream is initially positioned after the tag specified by the codec.
418 /// This method will continue reading entries from the stream until the end is reached, or
419 /// a different tag is encountered.
420 /// </remarks>
421 /// <param name="input">Stream to read from</param>
422 /// <param name="codec">Codec describing how the key/value pairs are encoded</param>
423 public void AddEntriesFrom(CodedInputStream input, Codec codec)
424 {
425 var adapter = new Codec.MessageAdapter(codec);
426 do
427 {
428 adapter.Reset();
429 input.ReadMessage(adapter);
430 this[adapter.Key] = adapter.Value;
431 } while (input.MaybeConsumeTag(codec.MapTag));
432 }
433
434 /// <summary>
435 /// Writes the contents of this map to the given coded output stream, using the specified codec
436 /// to encode each entry.
437 /// </summary>
438 /// <param name="output">The output stream to write to.</param>
439 /// <param name="codec">The codec to use for each entry.</param>
440 public void WriteTo(CodedOutputStream output, Codec codec)
441 {
442 var message = new Codec.MessageAdapter(codec);
443 foreach (var entry in list)
444 {
445 message.Key = entry.Key;
446 message.Value = entry.Value;
447 output.WriteTag(codec.MapTag);
448 output.WriteMessage(message);
449 }
450 }
451
452 /// <summary>
453 /// Calculates the size of this map based on the given entry codec.
454 /// </summary>
455 /// <param name="codec">The codec to use to encode each entry.</param>
456 /// <returns></returns>
457 public int CalculateSize(Codec codec)
458 {
459 if (Count == 0)
460 {
461 return 0;
462 }
463 var message = new Codec.MessageAdapter(codec);
464 int size = 0;
465 foreach (var entry in list)
466 {
467 message.Key = entry.Key;
468 message.Value = entry.Value;
469 size += CodedOutputStream.ComputeRawVarint32Size(codec.MapTag);
470 size += CodedOutputStream.ComputeMessageSize(message);
471 }
472 return size;
473 }
474
475 /// <summary>
476 /// Returns a string representation of this repeated field, in the same
477 /// way as it would be represented by the default JSON formatter.
478 /// </summary>
479 public override string ToString()
480 {
Austin Schuh40c16522018-10-28 20:27:54 -0700481 var writer = new StringWriter();
482 JsonFormatter.Default.WriteDictionary(writer, this);
483 return writer.ToString();
Brian Silverman9c614bc2016-02-15 20:20:02 -0500484 }
485
486 #region IDictionary explicit interface implementation
487 void IDictionary.Add(object key, object value)
488 {
489 Add((TKey)key, (TValue)value);
490 }
491
492 bool IDictionary.Contains(object key)
493 {
494 if (!(key is TKey))
495 {
496 return false;
497 }
498 return ContainsKey((TKey)key);
499 }
500
501 IDictionaryEnumerator IDictionary.GetEnumerator()
502 {
503 return new DictionaryEnumerator(GetEnumerator());
504 }
505
506 void IDictionary.Remove(object key)
507 {
Austin Schuh40c16522018-10-28 20:27:54 -0700508 ProtoPreconditions.CheckNotNull(key, nameof(key));
Brian Silverman9c614bc2016-02-15 20:20:02 -0500509 if (!(key is TKey))
510 {
511 return;
512 }
513 Remove((TKey)key);
514 }
515
516 void ICollection.CopyTo(Array array, int index)
517 {
518 // This is ugly and slow as heck, but with any luck it will never be used anyway.
519 ICollection temp = this.Select(pair => new DictionaryEntry(pair.Key, pair.Value)).ToList();
520 temp.CopyTo(array, index);
521 }
522
523 bool IDictionary.IsFixedSize { get { return false; } }
524
525 ICollection IDictionary.Keys { get { return (ICollection)Keys; } }
526
527 ICollection IDictionary.Values { get { return (ICollection)Values; } }
528
529 bool ICollection.IsSynchronized { get { return false; } }
530
531 object ICollection.SyncRoot { get { return this; } }
532
533 object IDictionary.this[object key]
534 {
535 get
536 {
Austin Schuh40c16522018-10-28 20:27:54 -0700537 ProtoPreconditions.CheckNotNull(key, nameof(key));
Brian Silverman9c614bc2016-02-15 20:20:02 -0500538 if (!(key is TKey))
539 {
540 return null;
541 }
542 TValue value;
543 TryGetValue((TKey)key, out value);
544 return value;
545 }
546
547 set
548 {
549 this[(TKey)key] = (TValue)value;
550 }
551 }
552 #endregion
553
Austin Schuh40c16522018-10-28 20:27:54 -0700554 #region IReadOnlyDictionary explicit interface implementation
555#if !NET35
556 IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => Keys;
557
558 IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values;
559#endif
560 #endregion
561
Brian Silverman9c614bc2016-02-15 20:20:02 -0500562 private class DictionaryEnumerator : IDictionaryEnumerator
563 {
564 private readonly IEnumerator<KeyValuePair<TKey, TValue>> enumerator;
565
566 internal DictionaryEnumerator(IEnumerator<KeyValuePair<TKey, TValue>> enumerator)
567 {
568 this.enumerator = enumerator;
569 }
570
571 public bool MoveNext()
572 {
573 return enumerator.MoveNext();
574 }
575
576 public void Reset()
577 {
578 enumerator.Reset();
579 }
580
581 public object Current { get { return Entry; } }
582 public DictionaryEntry Entry { get { return new DictionaryEntry(Key, Value); } }
583 public object Key { get { return enumerator.Current.Key; } }
584 public object Value { get { return enumerator.Current.Value; } }
585 }
586
587 /// <summary>
588 /// A codec for a specific map field. This contains all the information required to encode and
589 /// decode the nested messages.
590 /// </summary>
591 public sealed class Codec
592 {
593 private readonly FieldCodec<TKey> keyCodec;
594 private readonly FieldCodec<TValue> valueCodec;
595 private readonly uint mapTag;
596
597 /// <summary>
598 /// Creates a new entry codec based on a separate key codec and value codec,
599 /// and the tag to use for each map entry.
600 /// </summary>
601 /// <param name="keyCodec">The key codec.</param>
602 /// <param name="valueCodec">The value codec.</param>
603 /// <param name="mapTag">The map tag to use to introduce each map entry.</param>
604 public Codec(FieldCodec<TKey> keyCodec, FieldCodec<TValue> valueCodec, uint mapTag)
605 {
606 this.keyCodec = keyCodec;
607 this.valueCodec = valueCodec;
608 this.mapTag = mapTag;
609 }
610
611 /// <summary>
612 /// The tag used in the enclosing message to indicate map entries.
613 /// </summary>
614 internal uint MapTag { get { return mapTag; } }
615
616 /// <summary>
617 /// A mutable message class, used for parsing and serializing. This
618 /// delegates the work to a codec, but implements the <see cref="IMessage"/> interface
619 /// for interop with <see cref="CodedInputStream"/> and <see cref="CodedOutputStream"/>.
620 /// This is nested inside Codec as it's tightly coupled to the associated codec,
621 /// and it's simpler if it has direct access to all its fields.
622 /// </summary>
623 internal class MessageAdapter : IMessage
624 {
625 private static readonly byte[] ZeroLengthMessageStreamData = new byte[] { 0 };
626
627 private readonly Codec codec;
628 internal TKey Key { get; set; }
629 internal TValue Value { get; set; }
630
631 internal MessageAdapter(Codec codec)
632 {
633 this.codec = codec;
634 }
635
636 internal void Reset()
637 {
638 Key = codec.keyCodec.DefaultValue;
639 Value = codec.valueCodec.DefaultValue;
640 }
641
642 public void MergeFrom(CodedInputStream input)
643 {
644 uint tag;
645 while ((tag = input.ReadTag()) != 0)
646 {
647 if (tag == codec.keyCodec.Tag)
648 {
649 Key = codec.keyCodec.Read(input);
650 }
651 else if (tag == codec.valueCodec.Tag)
652 {
653 Value = codec.valueCodec.Read(input);
654 }
655 else
656 {
657 input.SkipLastField();
658 }
659 }
660
661 // Corner case: a map entry with a key but no value, where the value type is a message.
662 // Read it as if we'd seen an input stream with no data (i.e. create a "default" message).
663 if (Value == null)
664 {
665 Value = codec.valueCodec.Read(new CodedInputStream(ZeroLengthMessageStreamData));
666 }
667 }
668
669 public void WriteTo(CodedOutputStream output)
670 {
671 codec.keyCodec.WriteTagAndValue(output, Key);
672 codec.valueCodec.WriteTagAndValue(output, Value);
673 }
674
675 public int CalculateSize()
676 {
677 return codec.keyCodec.CalculateSizeWithTag(Key) + codec.valueCodec.CalculateSizeWithTag(Value);
678 }
679
680 MessageDescriptor IMessage.Descriptor { get { return null; } }
681 }
682 }
683
684 private class MapView<T> : ICollection<T>, ICollection
685 {
686 private readonly MapField<TKey, TValue> parent;
687 private readonly Func<KeyValuePair<TKey, TValue>, T> projection;
688 private readonly Func<T, bool> containsCheck;
689
690 internal MapView(
691 MapField<TKey, TValue> parent,
692 Func<KeyValuePair<TKey, TValue>, T> projection,
693 Func<T, bool> containsCheck)
694 {
695 this.parent = parent;
696 this.projection = projection;
697 this.containsCheck = containsCheck;
698 }
699
700 public int Count { get { return parent.Count; } }
701
702 public bool IsReadOnly { get { return true; } }
703
704 public bool IsSynchronized { get { return false; } }
705
706 public object SyncRoot { get { return parent; } }
707
708 public void Add(T item)
709 {
710 throw new NotSupportedException();
711 }
712
713 public void Clear()
714 {
715 throw new NotSupportedException();
716 }
717
718 public bool Contains(T item)
719 {
720 return containsCheck(item);
721 }
722
723 public void CopyTo(T[] array, int arrayIndex)
724 {
725 if (arrayIndex < 0)
726 {
Austin Schuh40c16522018-10-28 20:27:54 -0700727 throw new ArgumentOutOfRangeException(nameof(arrayIndex));
Brian Silverman9c614bc2016-02-15 20:20:02 -0500728 }
Austin Schuh40c16522018-10-28 20:27:54 -0700729 if (arrayIndex + Count > array.Length)
Brian Silverman9c614bc2016-02-15 20:20:02 -0500730 {
Austin Schuh40c16522018-10-28 20:27:54 -0700731 throw new ArgumentException("Not enough space in the array", nameof(array));
Brian Silverman9c614bc2016-02-15 20:20:02 -0500732 }
733 foreach (var item in this)
734 {
735 array[arrayIndex++] = item;
736 }
737 }
738
739 public IEnumerator<T> GetEnumerator()
740 {
741 return parent.list.Select(projection).GetEnumerator();
742 }
743
744 public bool Remove(T item)
745 {
746 throw new NotSupportedException();
747 }
748
749 IEnumerator IEnumerable.GetEnumerator()
750 {
751 return GetEnumerator();
752 }
753
754 public void CopyTo(Array array, int index)
755 {
756 if (index < 0)
757 {
Austin Schuh40c16522018-10-28 20:27:54 -0700758 throw new ArgumentOutOfRangeException(nameof(index));
Brian Silverman9c614bc2016-02-15 20:20:02 -0500759 }
Austin Schuh40c16522018-10-28 20:27:54 -0700760 if (index + Count > array.Length)
Brian Silverman9c614bc2016-02-15 20:20:02 -0500761 {
Austin Schuh40c16522018-10-28 20:27:54 -0700762 throw new ArgumentException("Not enough space in the array", nameof(array));
Brian Silverman9c614bc2016-02-15 20:20:02 -0500763 }
764 foreach (var item in this)
765 {
766 array.SetValue(item, index++);
767 }
768 }
769 }
770 }
771}