MapField.cs 28.9 KB
Newer Older
Jon Skeet's avatar
Jon Skeet committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
#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.
#endregion

33
using Google.Protobuf.Compatibility;
Jon Skeet's avatar
Jon Skeet committed
34
using Google.Protobuf.Reflection;
Jon Skeet's avatar
Jon Skeet committed
35 36 37
using System;
using System.Collections;
using System.Collections.Generic;
38
using System.IO;
Jon Skeet's avatar
Jon Skeet committed
39 40 41 42 43 44 45
using System.Linq;

namespace Google.Protobuf.Collections
{
    /// <summary>
    /// Representation of a map field in a Protocol Buffer message.
    /// </summary>
46 47
    /// <typeparam name="TKey">Key type in the map. Must be a type supported by Protocol Buffer map keys.</typeparam>
    /// <typeparam name="TValue">Value type in the map. Must be a type supported by Protocol Buffers.</typeparam>
Jon Skeet's avatar
Jon Skeet committed
48
    /// <remarks>
49
    /// <para>
50
    /// For string keys, the equality comparison is provided by <see cref="StringComparer.Ordinal" />.
51 52
    /// </para>
    /// <para>
53 54 55 56 57 58 59
    /// 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.
    /// </para>
    /// <para>
60 61 62 63
    /// 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 <code>byte</code>) but nor does it guarantee
    /// that all operations will work in such cases.
    /// </para>
64 65 66 67
    /// <para>
    /// The order in which entries are returned when iterating over this object is undefined, and may change
    /// in future versions.
    /// </para>
Jon Skeet's avatar
Jon Skeet committed
68
    /// </remarks>
69
    public sealed class MapField<TKey, TValue> : IDeepCloneable<MapField<TKey, TValue>>, IDictionary<TKey, TValue>, IEquatable<MapField<TKey, TValue>>, IDictionary
70 71 72
#if !NET35
        , IReadOnlyDictionary<TKey, TValue>
#endif
Jon Skeet's avatar
Jon Skeet committed
73
    {
74 75 76
        private static readonly EqualityComparer<TValue> ValueEqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<TValue>();
        private static readonly EqualityComparer<TKey> KeyEqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<TKey>();

Jon Skeet's avatar
Jon Skeet committed
77 78
        // TODO: Don't create the map/list until we have an entry. (Assume many maps will be empty.)
        private readonly Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> map =
79
            new Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>(KeyEqualityComparer);
Jon Skeet's avatar
Jon Skeet committed
80 81
        private readonly LinkedList<KeyValuePair<TKey, TValue>> list = new LinkedList<KeyValuePair<TKey, TValue>>();

82 83 84 85 86 87
        /// <summary>
        /// Creates a deep clone of this object.
        /// </summary>
        /// <returns>
        /// A deep clone of this object.
        /// </returns>
Jon Skeet's avatar
Jon Skeet committed
88 89
        public MapField<TKey, TValue> Clone()
        {
90
            var clone = new MapField<TKey, TValue>();
Jon Skeet's avatar
Jon Skeet committed
91 92 93 94 95
            // Keys are never cloneable. Values might be.
            if (typeof(IDeepCloneable<TValue>).IsAssignableFrom(typeof(TValue)))
            {
                foreach (var pair in list)
                {
96
                    clone.Add(pair.Key, ((IDeepCloneable<TValue>)pair.Value).Clone());
Jon Skeet's avatar
Jon Skeet committed
97 98 99 100 101 102 103 104 105 106
                }
            }
            else
            {
                // Nothing is cloneable, so we don't need to worry.
                clone.Add(this);
            }
            return clone;
        }

107 108 109 110 111 112 113 114 115
        /// <summary>
        /// Adds the specified key/value pair to the map.
        /// </summary>
        /// <remarks>
        /// This operation fails if the key already exists in the map. To replace an existing entry, use the indexer.
        /// </remarks>
        /// <param name="key">The key to add</param>
        /// <param name="value">The value to add.</param>
        /// <exception cref="System.ArgumentException">The given key already exists in map.</exception>
Jon Skeet's avatar
Jon Skeet committed
116 117
        public void Add(TKey key, TValue value)
        {
118
            // Validation of arguments happens in ContainsKey and the indexer
Jon Skeet's avatar
Jon Skeet committed
119 120
            if (ContainsKey(key))
            {
121
                throw new ArgumentException("Key already exists in map", nameof(key));
Jon Skeet's avatar
Jon Skeet committed
122 123 124 125
            }
            this[key] = value;
        }

126 127 128 129 130
        /// <summary>
        /// Determines whether the specified key is present in the map.
        /// </summary>
        /// <param name="key">The key to check.</param>
        /// <returns><c>true</c> if the map contains the given key; <c>false</c> otherwise.</returns>
Jon Skeet's avatar
Jon Skeet committed
131 132
        public bool ContainsKey(TKey key)
        {
133
            ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
Jon Skeet's avatar
Jon Skeet committed
134 135 136
            return map.ContainsKey(key);
        }

137 138
        private bool ContainsValue(TValue value) =>
            list.Any(pair => ValueEqualityComparer.Equals(pair.Value, value));
139

140 141 142 143 144
        /// <summary>
        /// Removes the entry identified by the given key from the map.
        /// </summary>
        /// <param name="key">The key indicating the entry to remove from the map.</param>
        /// <returns><c>true</c> if the map contained the given key before the entry was removed; <c>false</c> otherwise.</returns>
Jon Skeet's avatar
Jon Skeet committed
145 146
        public bool Remove(TKey key)
        {
147
            ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
Jon Skeet's avatar
Jon Skeet committed
148 149 150 151 152 153 154 155 156 157 158 159 160
            LinkedListNode<KeyValuePair<TKey, TValue>> node;
            if (map.TryGetValue(key, out node))
            {
                map.Remove(key);
                node.List.Remove(node);
                return true;
            }
            else
            {
                return false;
            }
        }

161 162 163 164 165 166 167 168
        /// <summary>
        /// Gets the value associated with the specified key.
        /// </summary>
        /// <param name="key">The key whose value to get.</param>
        /// <param name="value">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 <paramref name="value"/> parameter.
        /// This parameter is passed uninitialized.</param>
        /// <returns><c>true</c> if the map contains an element with the specified key; otherwise, <c>false</c>.</returns>
Jon Skeet's avatar
Jon Skeet committed
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
        public bool TryGetValue(TKey key, out TValue value)
        {
            LinkedListNode<KeyValuePair<TKey, TValue>> node;
            if (map.TryGetValue(key, out node))
            {
                value = node.Value.Value;
                return true;
            }
            else
            {
                value = default(TValue);
                return false;
            }
        }

184 185 186 187 188 189 190
        /// <summary>
        /// Gets or sets the value associated with the specified key.
        /// </summary>
        /// <param name="key">The key of the value to get or set.</param>
        /// <exception cref="KeyNotFoundException">The property is retrieved and key does not exist in the collection.</exception>
        /// <returns>The value associated with the specified key. If the specified key is not found,
        /// a get operation throws a <see cref="KeyNotFoundException"/>, and a set operation creates a new element with the specified key.</returns>
Jon Skeet's avatar
Jon Skeet committed
191 192 193 194
        public TValue this[TKey key]
        {
            get
            {
195
                ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
Jon Skeet's avatar
Jon Skeet committed
196 197 198 199 200 201 202 203 204
                TValue value;
                if (TryGetValue(key, out value))
                {
                    return value;
                }
                throw new KeyNotFoundException();
            }
            set
            {
205
                ProtoPreconditions.CheckNotNullUnconstrained(key, nameof(key));
206
                // value == null check here is redundant, but avoids boxing.
207
                if (value == null)
208
                {
209
                    ProtoPreconditions.CheckNotNullUnconstrained(value, nameof(value));
210
                }
Jon Skeet's avatar
Jon Skeet committed
211 212 213 214 215 216 217 218 219 220 221 222 223 224
                LinkedListNode<KeyValuePair<TKey, TValue>> node;
                var pair = new KeyValuePair<TKey, TValue>(key, value);
                if (map.TryGetValue(key, out node))
                {
                    node.Value = pair;
                }
                else
                {
                    node = list.AddLast(pair);
                    map[key] = node;
                }
            }
        }

225 226 227
        /// <summary>
        /// Gets a collection containing the keys in the map.
        /// </summary>
228
        public ICollection<TKey> Keys { get { return new MapView<TKey>(this, pair => pair.Key, ContainsKey); } }
229 230 231 232

        /// <summary>
        /// Gets a collection containing the values in the map.
        /// </summary>
233
        public ICollection<TValue> Values { get { return new MapView<TValue>(this, pair => pair.Value, ContainsValue); } }
Jon Skeet's avatar
Jon Skeet committed
234

235
        /// <summary>
236
        /// Adds the specified entries to the map. The keys and values are not automatically cloned.
237 238
        /// </summary>
        /// <param name="entries">The entries to add to the map.</param>
Jon Skeet's avatar
Jon Skeet committed
239 240
        public void Add(IDictionary<TKey, TValue> entries)
        {
241
            ProtoPreconditions.CheckNotNull(entries, nameof(entries));
Jon Skeet's avatar
Jon Skeet committed
242 243
            foreach (var pair in entries)
            {
244
                Add(pair.Key, pair.Value);
Jon Skeet's avatar
Jon Skeet committed
245 246 247
            }
        }

248 249 250 251 252 253
        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// An enumerator that can be used to iterate through the collection.
        /// </returns>
Jon Skeet's avatar
Jon Skeet committed
254 255 256 257 258
        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            return list.GetEnumerator();
        }

259 260 261 262 263 264
        /// <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>
Jon Skeet's avatar
Jon Skeet committed
265 266 267 268 269
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

270 271 272 273
        /// <summary>
        /// Adds the specified item to the map.
        /// </summary>
        /// <param name="item">The item to add to the map.</param>
274
        void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
Jon Skeet's avatar
Jon Skeet committed
275 276 277 278
        {
            Add(item.Key, item.Value);
        }

279 280 281
        /// <summary>
        /// Removes all items from the map.
        /// </summary>
Jon Skeet's avatar
Jon Skeet committed
282 283 284 285 286 287
        public void Clear()
        {
            list.Clear();
            map.Clear();
        }

288 289 290 291 292
        /// <summary>
        /// Determines whether map contains an entry equivalent to the given key/value pair.
        /// </summary>
        /// <param name="item">The key/value pair to find.</param>
        /// <returns></returns>
293
        bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
Jon Skeet's avatar
Jon Skeet committed
294 295
        {
            TValue value;
296
            return TryGetValue(item.Key, out value) && ValueEqualityComparer.Equals(item.Value, value);
Jon Skeet's avatar
Jon Skeet committed
297 298
        }

299 300 301 302 303
        /// <summary>
        /// Copies the key/value pairs in this map to an array.
        /// </summary>
        /// <param name="array">The array to copy the entries into.</param>
        /// <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)
Jon Skeet's avatar
Jon Skeet committed
305 306 307 308
        {
            list.CopyTo(array, arrayIndex);
        }

309 310 311 312 313 314
        /// <summary>
        /// Removes the specified key/value pair from the map.
        /// </summary>
        /// <remarks>Both the key and the value must be found for the entry to be removed.</remarks>
        /// <param name="item">The key/value pair to remove.</param>
        /// <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)
Jon Skeet's avatar
Jon Skeet committed
316
        {
317 318
            if (item.Key == null)
            {
319
                throw new ArgumentException("Key is null", nameof(item));
320 321 322 323 324 325 326 327 328 329 330 331 332
            }
            LinkedListNode<KeyValuePair<TKey, TValue>> node;
            if (map.TryGetValue(item.Key, out node) &&
                EqualityComparer<TValue>.Default.Equals(item.Value, node.Value.Value))
            {
                map.Remove(item.Key);
                node.List.Remove(node);
                return true;
            }
            else
            {
                return false;
            }
Jon Skeet's avatar
Jon Skeet committed
333 334
        }

335 336 337
        /// <summary>
        /// Gets the number of elements contained in the map.
        /// </summary>
Jon Skeet's avatar
Jon Skeet committed
338
        public int Count { get { return list.Count; } }
339 340 341 342

        /// <summary>
        /// Gets a value indicating whether the map is read-only.
        /// </summary>
343
        public bool IsReadOnly { get { return false; } }
Jon Skeet's avatar
Jon Skeet committed
344

345 346 347 348 349 350 351
        /// <summary>
        /// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
        /// </summary>
        /// <param name="other">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>
Jon Skeet's avatar
Jon Skeet committed
352 353 354 355 356
        public override bool Equals(object other)
        {
            return Equals(other as MapField<TKey, TValue>);
        }

357 358 359 360 361 362
        /// <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>
Jon Skeet's avatar
Jon Skeet committed
363 364
        public override int GetHashCode()
        {
365 366
            var keyComparer = KeyEqualityComparer;
            var valueComparer = ValueEqualityComparer;
367
            int hash = 0;
Jon Skeet's avatar
Jon Skeet committed
368 369
            foreach (var pair in list)
            {
370
                hash ^= keyComparer.GetHashCode(pair.Key) * 31 + valueComparer.GetHashCode(pair.Value);
Jon Skeet's avatar
Jon Skeet committed
371 372 373 374
            }
            return hash;
        }

375 376 377 378 379 380 381 382
        /// <summary>
        /// Compares this map with another for equality.
        /// </summary>
        /// <remarks>
        /// The order of the key/value pairs in the maps is not deemed significant in this comparison.
        /// </remarks>
        /// <param name="other">The map to compare this with.</param>
        /// <returns><c>true</c> if <paramref name="other"/> refers to an equal map; <c>false</c> otherwise.</returns>
Jon Skeet's avatar
Jon Skeet committed
383 384 385 386 387 388 389 390 391 392 393 394 395 396
        public bool Equals(MapField<TKey, TValue> other)
        {
            if (other == null)
            {
                return false;
            }
            if (other == this)
            {
                return true;
            }
            if (other.Count != this.Count)
            {
                return false;
            }
397
            var valueComparer = ValueEqualityComparer;
Jon Skeet's avatar
Jon Skeet committed
398 399 400 401 402 403 404 405 406 407 408 409 410 411 412
            foreach (var pair in this)
            {
                TValue value;
                if (!other.TryGetValue(pair.Key, out value))
                {
                    return false;
                }
                if (!valueComparer.Equals(value, pair.Value))
                {
                    return false;
                }
            }
            return true;
        }

413 414 415 416 417 418 419 420 421 422
        /// <summary>
        /// Adds entries to the map from the given stream.
        /// </summary>
        /// <remarks>
        /// 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.
        /// </remarks>
        /// <param name="input">Stream to read from</param>
        /// <param name="codec">Codec describing how the key/value pairs are encoded</param>
423
        public void AddEntriesFrom(CodedInputStream input, Codec codec)
Jon Skeet's avatar
Jon Skeet committed
424 425
        {
            var adapter = new Codec.MessageAdapter(codec);
426 427 428 429 430 431
            do
            {
                adapter.Reset();
                input.ReadMessage(adapter);
                this[adapter.Key] = adapter.Value;
            } while (input.MaybeConsumeTag(codec.MapTag));
432
        }
Jon Skeet's avatar
Jon Skeet committed
433

434 435 436 437 438 439
        /// <summary>
        /// Writes the contents of this map to the given coded output stream, using the specified codec
        /// to encode each entry.
        /// </summary>
        /// <param name="output">The output stream to write to.</param>
        /// <param name="codec">The codec to use for each entry.</param>
Jon Skeet's avatar
Jon Skeet committed
440 441 442 443 444 445 446 447 448 449 450 451
        public void WriteTo(CodedOutputStream output, Codec codec)
        {
            var message = new Codec.MessageAdapter(codec);
            foreach (var entry in list)
            {
                message.Key = entry.Key;
                message.Value = entry.Value;
                output.WriteTag(codec.MapTag);
                output.WriteMessage(message);
            }
        }

452 453 454 455 456
        /// <summary>
        /// Calculates the size of this map based on the given entry codec.
        /// </summary>
        /// <param name="codec">The codec to use to encode each entry.</param>
        /// <returns></returns>
Jon Skeet's avatar
Jon Skeet committed
457 458
        public int CalculateSize(Codec codec)
        {
459 460 461 462
            if (Count == 0)
            {
                return 0;
            }
Jon Skeet's avatar
Jon Skeet committed
463 464 465 466 467 468 469 470 471 472 473 474
            var message = new Codec.MessageAdapter(codec);
            int size = 0;
            foreach (var entry in list)
            {
                message.Key = entry.Key;
                message.Value = entry.Value;
                size += CodedOutputStream.ComputeRawVarint32Size(codec.MapTag);
                size += CodedOutputStream.ComputeMessageSize(message);
            }
            return size;
        }

475 476 477 478 479 480
        /// <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 override string ToString()
        {
481 482 483
            var writer = new StringWriter();
            JsonFormatter.Default.WriteDictionary(writer, this);
            return writer.ToString();
484 485
        }

486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502
        #region IDictionary explicit interface implementation
        void IDictionary.Add(object key, object value)
        {
            Add((TKey)key, (TValue)value);
        }

        bool IDictionary.Contains(object key)
        {
            if (!(key is TKey))
            {
                return false;
            }
            return ContainsKey((TKey)key);
        }

        IDictionaryEnumerator IDictionary.GetEnumerator()
        {
503
            return new DictionaryEnumerator(GetEnumerator());
504 505 506 507
        }

        void IDictionary.Remove(object key)
        {
508
            ProtoPreconditions.CheckNotNull(key, nameof(key));
509 510 511 512 513 514 515 516 517
            if (!(key is TKey))
            {
                return;
            }
            Remove((TKey)key);
        }

        void ICollection.CopyTo(Array array, int index)
        {
518 519 520
            // This is ugly and slow as heck, but with any luck it will never be used anyway.
            ICollection temp = this.Select(pair => new DictionaryEntry(pair.Key, pair.Value)).ToList();
            temp.CopyTo(array, index);
521 522
        }

523
        bool IDictionary.IsFixedSize { get { return false; } }
524 525 526 527 528 529 530

        ICollection IDictionary.Keys { get { return (ICollection)Keys; } }

        ICollection IDictionary.Values { get { return (ICollection)Values; } }

        bool ICollection.IsSynchronized { get { return false; } }

531
        object ICollection.SyncRoot { get { return this; } }
532 533 534 535 536

        object IDictionary.this[object key]
        {
            get
            {
537
                ProtoPreconditions.CheckNotNull(key, nameof(key));
538 539 540 541 542 543 544 545 546
                if (!(key is TKey))
                {
                    return null;
                }
                TValue value;
                TryGetValue((TKey)key, out value);
                return value;
            }

547 548 549 550
            set
            {
                this[(TKey)key] = (TValue)value;
            }
551 552 553
        }
        #endregion

554 555 556 557 558 559 560 561
        #region IReadOnlyDictionary explicit interface implementation
#if !NET35
        IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => Keys;

        IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values;
#endif
        #endregion

562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586
        private class DictionaryEnumerator : IDictionaryEnumerator
        {
            private readonly IEnumerator<KeyValuePair<TKey, TValue>> enumerator;

            internal DictionaryEnumerator(IEnumerator<KeyValuePair<TKey, TValue>> enumerator)
            {
                this.enumerator = enumerator;
            }

            public bool MoveNext()
            {
                return enumerator.MoveNext();
            }

            public void Reset()
            {
                enumerator.Reset();
            }

            public object Current { get { return Entry; } }
            public DictionaryEntry Entry { get { return new DictionaryEntry(Key, Value); } }
            public object Key { get { return enumerator.Current.Key; } }
            public object Value { get { return enumerator.Current.Value; } }
        }

Jon Skeet's avatar
Jon Skeet committed
587
        /// <summary>
588
        /// A codec for a specific map field. This contains all the information required to encode and
Jon Skeet's avatar
Jon Skeet committed
589 590 591 592 593 594 595 596
        /// decode the nested messages.
        /// </summary>
        public sealed class Codec
        {
            private readonly FieldCodec<TKey> keyCodec;
            private readonly FieldCodec<TValue> valueCodec;
            private readonly uint mapTag;

597 598 599 600 601 602 603
            /// <summary>
            /// Creates a new entry codec based on a separate key codec and value codec,
            /// and the tag to use for each map entry.
            /// </summary>
            /// <param name="keyCodec">The key codec.</param>
            /// <param name="valueCodec">The value codec.</param>
            /// <param name="mapTag">The map tag to use to introduce each map entry.</param>
Jon Skeet's avatar
Jon Skeet committed
604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624
            public Codec(FieldCodec<TKey> keyCodec, FieldCodec<TValue> valueCodec, uint mapTag)
            {
                this.keyCodec = keyCodec;
                this.valueCodec = valueCodec;
                this.mapTag = mapTag;
            }

            /// <summary>
            /// The tag used in the enclosing message to indicate map entries.
            /// </summary>
            internal uint MapTag { get { return mapTag; } }

            /// <summary>
            /// A mutable message class, used for parsing and serializing. This
            /// delegates the work to a codec, but implements the <see cref="IMessage"/> interface
            /// for interop with <see cref="CodedInputStream"/> and <see cref="CodedOutputStream"/>.
            /// 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.
            /// </summary>
            internal class MessageAdapter : IMessage
            {
625 626
                private static readonly byte[] ZeroLengthMessageStreamData = new byte[] { 0 };

Jon Skeet's avatar
Jon Skeet committed
627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644
                private readonly Codec codec;
                internal TKey Key { get; set; }
                internal TValue Value { get; set; }

                internal MessageAdapter(Codec codec)
                {
                    this.codec = codec;
                }

                internal void Reset()
                {
                    Key = codec.keyCodec.DefaultValue;
                    Value = codec.valueCodec.DefaultValue;
                }

                public void MergeFrom(CodedInputStream input)
                {
                    uint tag;
645
                    while ((tag = input.ReadTag()) != 0)
Jon Skeet's avatar
Jon Skeet committed
646 647 648 649 650 651 652 653 654
                    {
                        if (tag == codec.keyCodec.Tag)
                        {
                            Key = codec.keyCodec.Read(input);
                        }
                        else if (tag == codec.valueCodec.Tag)
                        {
                            Value = codec.valueCodec.Read(input);
                        }
Jon Skeet's avatar
Jon Skeet committed
655
                        else 
Jon Skeet's avatar
Jon Skeet committed
656
                        {
Jon Skeet's avatar
Jon Skeet committed
657
                            input.SkipLastField();
Jon Skeet's avatar
Jon Skeet committed
658 659
                        }
                    }
660 661 662 663 664 665 666

                    // 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 == null)
                    {
                        Value = codec.valueCodec.Read(new CodedInputStream(ZeroLengthMessageStreamData));
                    }
Jon Skeet's avatar
Jon Skeet committed
667 668 669 670
                }

                public void WriteTo(CodedOutputStream output)
                {
671 672
                    codec.keyCodec.WriteTagAndValue(output, Key);
                    codec.valueCodec.WriteTagAndValue(output, Value);
Jon Skeet's avatar
Jon Skeet committed
673 674 675 676
                }

                public int CalculateSize()
                {
677
                    return codec.keyCodec.CalculateSizeWithTag(Key) + codec.valueCodec.CalculateSizeWithTag(Value);
Jon Skeet's avatar
Jon Skeet committed
678
                }
Jon Skeet's avatar
Jon Skeet committed
679 680

                MessageDescriptor IMessage.Descriptor { get { return null; } }
Jon Skeet's avatar
Jon Skeet committed
681 682
            }
        }
683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726

        private class MapView<T> : ICollection<T>, ICollection
        {
            private readonly MapField<TKey, TValue> parent;
            private readonly Func<KeyValuePair<TKey, TValue>, T> projection;
            private readonly Func<T, bool> containsCheck;

            internal MapView(
                MapField<TKey, TValue> parent,
                Func<KeyValuePair<TKey, TValue>, T> projection,
                Func<T, bool> containsCheck)
            {
                this.parent = parent;
                this.projection = projection;
                this.containsCheck = containsCheck;
            }

            public int Count { get { return parent.Count; } }

            public bool IsReadOnly { get { return true; } }

            public bool IsSynchronized { get { return false; } }

            public object SyncRoot { get { return parent; } }

            public void Add(T item)
            {
                throw new NotSupportedException();
            }

            public void Clear()
            {
                throw new NotSupportedException();
            }

            public bool Contains(T item)
            {
                return containsCheck(item);
            }

            public void CopyTo(T[] array, int arrayIndex)
            {
                if (arrayIndex < 0)
                {
727
                    throw new ArgumentOutOfRangeException(nameof(arrayIndex));
728
                }
729
                if (arrayIndex + Count > array.Length)
730
                {
731
                    throw new ArgumentException("Not enough space in the array", nameof(array));
732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755
                }
                foreach (var item in this)
                {
                    array[arrayIndex++] = item;
                }
            }

            public IEnumerator<T> GetEnumerator()
            {
                return parent.list.Select(projection).GetEnumerator();
            }

            public bool Remove(T item)
            {
                throw new NotSupportedException();
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

            public void CopyTo(Array array, int index)
            {
756 757
                if (index < 0)
                {
758
                    throw new ArgumentOutOfRangeException(nameof(index));
759
                }
760
                if (index + Count > array.Length)
761
                {
762
                    throw new ArgumentException("Not enough space in the array", nameof(array));
763 764 765 766 767
                }
                foreach (var item in this)
                {
                    array.SetValue(item, index++);
                }
768 769
            }
        }
Jon Skeet's avatar
Jon Skeet committed
770 771
    }
}