#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 using System; using System.Collections; using System.Collections.Generic; using System.Linq; namespace Google.Protobuf.Collections { /// /// Representation of a map field in a Protocol Buffer message. /// /// /// This implementation preserves insertion order for simplicity of testing /// code using maps fields. Overwriting an existing entry does not change the /// position of that entry within the map. Equality is not order-sensitive. /// For string keys, the equality comparison is provided by . /// /// 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. public sealed class MapField : IDeepCloneable>, IFreezable, IDictionary, IEquatable> { // TODO: Don't create the map/list until we have an entry. (Assume many maps will be empty.) private bool frozen; private readonly Dictionary>> map = new Dictionary>>(); private readonly LinkedList> list = new LinkedList>(); public MapField Clone() { var clone = new MapField(); // Keys are never cloneable. Values might be. if (typeof(IDeepCloneable).IsAssignableFrom(typeof(TValue))) { foreach (var pair in list) { clone.Add(pair.Key, pair.Value == null ? pair.Value : ((IDeepCloneable) pair.Value).Clone()); } } else { // Nothing is cloneable, so we don't need to worry. clone.Add(this); } return clone; } public void Add(TKey key, TValue value) { // Validation of arguments happens in ContainsKey and the indexer if (ContainsKey(key)) { throw new ArgumentException("Key already exists in map", "key"); } this[key] = value; } public bool ContainsKey(TKey key) { ThrowHelper.ThrowIfNull(key, "key"); return map.ContainsKey(key); } public bool Remove(TKey key) { this.CheckMutable(); ThrowHelper.ThrowIfNull(key, "key"); LinkedListNode> node; if (map.TryGetValue(key, out node)) { map.Remove(key); node.List.Remove(node); return true; } else { return false; } } public bool TryGetValue(TKey key, out TValue value) { LinkedListNode> node; if (map.TryGetValue(key, out node)) { value = node.Value.Value; return true; } else { value = default(TValue); return false; } } public TValue this[TKey key] { get { ThrowHelper.ThrowIfNull(key, "key"); TValue value; if (TryGetValue(key, out value)) { return value; } throw new KeyNotFoundException(); } set { ThrowHelper.ThrowIfNull(key, "key"); if (value == null && (typeof(TValue) == typeof(ByteString) || typeof(TValue) == typeof(string))) { ThrowHelper.ThrowIfNull(value, "value"); } this.CheckMutable(); LinkedListNode> node; var pair = new KeyValuePair(key, value); if (map.TryGetValue(key, out node)) { node.Value = pair; } else { node = list.AddLast(pair); map[key] = node; } } } // TODO: Make these views? public ICollection Keys { get { return list.Select(t => t.Key).ToList(); } } public ICollection Values { get { return list.Select(t => t.Value).ToList(); } } public void Add(IDictionary entries) { ThrowHelper.ThrowIfNull(entries, "entries"); foreach (var pair in entries) { Add(pair.Key, pair.Value); } } public IEnumerator> GetEnumerator() { return list.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } void ICollection>.Add(KeyValuePair item) { Add(item.Key, item.Value); } public void Clear() { this.CheckMutable(); list.Clear(); map.Clear(); } bool ICollection>.Contains(KeyValuePair item) { TValue value; return TryGetValue(item.Key, out value) && EqualityComparer.Default.Equals(item.Value, value); } void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { list.CopyTo(array, arrayIndex); } bool ICollection>.Remove(KeyValuePair item) { this.CheckMutable(); if (item.Key == null) { throw new ArgumentException("Key is null", "item"); } LinkedListNode> node; if (map.TryGetValue(item.Key, out node) && EqualityComparer.Default.Equals(item.Value, node.Value.Value)) { map.Remove(item.Key); node.List.Remove(node); return true; } else { return false; } } public int Count { get { return list.Count; } } public bool IsReadOnly { get { return frozen; } } public void Freeze() { if (IsFrozen) { return; } frozen = true; // Only values can be frozen, as all the key types are simple. // Everything can be done in-place, as we're just freezing objects. if (typeof(IFreezable).IsAssignableFrom(typeof(TValue))) { for (var node = list.First; node != null; node = node.Next) { var pair = node.Value; IFreezable freezableValue = pair.Value as IFreezable; if (freezableValue != null) { freezableValue.Freeze(); } } } } public bool IsFrozen { get { return frozen; } } public override bool Equals(object other) { return Equals(other as MapField); } public override int GetHashCode() { var valueComparer = EqualityComparer.Default; int hash = 19; foreach (var pair in list) { hash ^= pair.Key.GetHashCode() * 31 + valueComparer.GetHashCode(pair.Value); } return hash; } public bool Equals(MapField other) { if (other == null) { return false; } if (other == this) { return true; } if (other.Count != this.Count) { return false; } var valueComparer = EqualityComparer.Default; 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; } /// /// 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 void AddEntriesFrom(CodedInputStream input, Codec codec) { var adapter = new Codec.MessageAdapter(codec); do { adapter.Reset(); input.ReadMessage(adapter); this[adapter.Key] = adapter.Value; } while (input.MaybeConsumeTag(codec.MapTag)); } 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); } } public int CalculateSize(Codec codec) { 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; } /// /// A codec for a specific map field. This contains all the information required to encoded and /// decode the nested messages. /// public sealed class Codec { private readonly FieldCodec keyCodec; private readonly FieldCodec valueCodec; private readonly uint mapTag; public Codec(FieldCodec keyCodec, FieldCodec valueCodec, uint mapTag) { this.keyCodec = keyCodec; this.valueCodec = valueCodec; this.mapTag = mapTag; } /// /// The tag used in the enclosing message to indicate map entries. /// internal uint MapTag { get { return mapTag; } } /// /// 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. /// internal class MessageAdapter : IMessage { private readonly Codec codec; internal TKey Key { get; set; } internal TValue Value { get; set; } internal int Size { 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; while (input.ReadTag(out tag)) { if (tag == 0) { throw InvalidProtocolBufferException.InvalidTag(); } if (tag == codec.keyCodec.Tag) { Key = codec.keyCodec.Read(input); } else if (tag == codec.valueCodec.Tag) { Value = codec.valueCodec.Read(input); } else if (WireFormat.IsEndGroupTag(tag)) { // TODO(jonskeet): Do we need this? (Given that we don't support groups...) return; } } } public void WriteTo(CodedOutputStream output) { codec.keyCodec.Write(output, Key); codec.valueCodec.Write(output, Value); } public int CalculateSize() { return codec.keyCodec.CalculateSize(Key) + codec.valueCodec.CalculateSize(Value); } } } } }