diff options
Diffstat (limited to 'csharp/src/Google.Protobuf/Collections')
3 files changed, 1182 insertions, 0 deletions
diff --git a/csharp/src/Google.Protobuf/Collections/MapField.cs b/csharp/src/Google.Protobuf/Collections/MapField.cs new file mode 100644 index 00000000..f5e4fd3a --- /dev/null +++ b/csharp/src/Google.Protobuf/Collections/MapField.cs @@ -0,0 +1,565 @@ +#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 +{ + /// <summary> + /// Representation of a map field in a Protocol Buffer message. + /// </summary> + /// <remarks> + /// 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 <see cref="StringComparer.Ordinal"/>. + /// </remarks> + /// <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> + public sealed class MapField<TKey, TValue> : IDeepCloneable<MapField<TKey, TValue>>, IFreezable, IDictionary<TKey, TValue>, IEquatable<MapField<TKey, TValue>>, IDictionary + { + // TODO: Don't create the map/list until we have an entry. (Assume many maps will be empty.) + private readonly bool allowNullValues; + private bool frozen; + private readonly Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> map = + new Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>(); + private readonly LinkedList<KeyValuePair<TKey, TValue>> list = new LinkedList<KeyValuePair<TKey, TValue>>(); + + /// <summary> + /// Constructs a new map field, defaulting the value nullability to only allow null values for message types + /// and non-nullable value types. + /// </summary> + public MapField() : this(typeof(IMessage).IsAssignableFrom(typeof(TValue)) || Nullable.GetUnderlyingType(typeof(TValue)) != null) + { + } + + /// <summary> + /// Constructs a new map field, overriding the choice of whether null values are permitted in the map. + /// This is used by wrapper types, where maps with string and bytes wrappers as the value types + /// support null values. + /// </summary> + /// <param name="allowNullValues">Whether null values are permitted in the map or not.</param> + public MapField(bool allowNullValues) + { + if (allowNullValues && typeof(TValue).IsValueType && Nullable.GetUnderlyingType(typeof(TValue)) == null) + { + throw new ArgumentException("allowNullValues", "Non-nullable value types do not support null values"); + } + this.allowNullValues = allowNullValues; + } + + public MapField<TKey, TValue> Clone() + { + var clone = new MapField<TKey, TValue>(allowNullValues); + // Keys are never cloneable. Values might be. + if (typeof(IDeepCloneable<TValue>).IsAssignableFrom(typeof(TValue))) + { + foreach (var pair in list) + { + clone.Add(pair.Key, pair.Value == null ? pair.Value : ((IDeepCloneable<TValue>)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<KeyValuePair<TKey, TValue>> 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<KeyValuePair<TKey, TValue>> 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"); + // value == null check here is redundant, but avoids boxing. + if (value == null && !allowNullValues) + { + ThrowHelper.ThrowIfNull(value, "value"); + } + this.CheckMutable(); + 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; + } + } + } + + // TODO: Make these views? + public ICollection<TKey> Keys { get { return list.Select(t => t.Key).ToList(); } } + public ICollection<TValue> Values { get { return list.Select(t => t.Value).ToList(); } } + + public void Add(IDictionary<TKey, TValue> entries) + { + ThrowHelper.ThrowIfNull(entries, "entries"); + foreach (var pair in entries) + { + Add(pair.Key, pair.Value); + } + } + + public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() + { + return list.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) + { + Add(item.Key, item.Value); + } + + public void Clear() + { + this.CheckMutable(); + list.Clear(); + map.Clear(); + } + + bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) + { + TValue value; + return TryGetValue(item.Key, out value) + && EqualityComparer<TValue>.Default.Equals(item.Value, value); + } + + void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) + { + list.CopyTo(array, arrayIndex); + } + + bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) + { + this.CheckMutable(); + if (item.Key == null) + { + throw new ArgumentException("Key is null", "item"); + } + 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; + } + } + + /// <summary> + /// Returns whether or not this map allows values to be null. + /// </summary> + public bool AllowsNullValues { get { return allowNullValues; } } + + 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<TKey, TValue>); + } + + public override int GetHashCode() + { + var valueComparer = EqualityComparer<TValue>.Default; + int hash = 0; + foreach (var pair in list) + { + hash ^= pair.Key.GetHashCode() * 31 + valueComparer.GetHashCode(pair.Value); + } + return hash; + } + + public bool Equals(MapField<TKey, TValue> other) + { + if (other == null) + { + return false; + } + if (other == this) + { + return true; + } + if (other.Count != this.Count) + { + return false; + } + var valueComparer = EqualityComparer<TValue>.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; + } + + /// <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> + 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) + { + if (Count == 0) + { + return 0; + } + 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; + } + + #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() + { + return new DictionaryEnumerator(GetEnumerator()); + } + + void IDictionary.Remove(object key) + { + ThrowHelper.ThrowIfNull(key, "key"); + this.CheckMutable(); + if (!(key is TKey)) + { + return; + } + Remove((TKey)key); + } + + void ICollection.CopyTo(Array array, int index) + { + // 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); + } + + bool IDictionary.IsFixedSize { get { return IsFrozen; } } + + ICollection IDictionary.Keys { get { return (ICollection)Keys; } } + + ICollection IDictionary.Values { get { return (ICollection)Values; } } + + bool ICollection.IsSynchronized { get { return false; } } + + object ICollection.SyncRoot { get { return this; } } + + object IDictionary.this[object key] + { + get + { + ThrowHelper.ThrowIfNull(key, "key"); + if (!(key is TKey)) + { + return null; + } + TValue value; + TryGetValue((TKey)key, out value); + return value; + } + + set + { + if (frozen) + { + throw new NotSupportedException("Dictionary is frozen"); + } + this[(TKey)key] = (TValue)value; + } + } + #endregion + + 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; } } + } + + /// <summary> + /// A codec for a specific map field. This contains all the information required to encoded and + /// decode the nested messages. + /// </summary> + public sealed class Codec + { + private readonly FieldCodec<TKey> keyCodec; + private readonly FieldCodec<TValue> valueCodec; + private readonly uint mapTag; + + 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 + { + 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; + 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.WriteTagAndValue(output, Key); + codec.valueCodec.WriteTagAndValue(output, Value); + } + + public int CalculateSize() + { + return codec.keyCodec.CalculateSizeWithTag(Key) + codec.valueCodec.CalculateSizeWithTag(Value); + } + } + } + } +} diff --git a/csharp/src/Google.Protobuf/Collections/ReadOnlyDictionary.cs b/csharp/src/Google.Protobuf/Collections/ReadOnlyDictionary.cs new file mode 100644 index 00000000..84360667 --- /dev/null +++ b/csharp/src/Google.Protobuf/Collections/ReadOnlyDictionary.cs @@ -0,0 +1,147 @@ +#region Copyright notice and license
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 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;
+
+namespace Google.Protobuf.Collections
+{
+ /// <summary>
+ /// Read-only wrapper around another dictionary.
+ /// </summary>
+ internal sealed class ReadOnlyDictionary<TKey, TValue> : IDictionary<TKey, TValue>
+ {
+ private readonly IDictionary<TKey, TValue> wrapped;
+
+ public ReadOnlyDictionary(IDictionary<TKey, TValue> wrapped)
+ {
+ this.wrapped = wrapped;
+ }
+
+ public void Add(TKey key, TValue value)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public bool ContainsKey(TKey key)
+ {
+ return wrapped.ContainsKey(key);
+ }
+
+ public ICollection<TKey> Keys
+ {
+ get { return wrapped.Keys; }
+ }
+
+ public bool Remove(TKey key)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public bool TryGetValue(TKey key, out TValue value)
+ {
+ return wrapped.TryGetValue(key, out value);
+ }
+
+ public ICollection<TValue> Values
+ {
+ get { return wrapped.Values; }
+ }
+
+ public TValue this[TKey key]
+ {
+ get { return wrapped[key]; }
+ set { throw new InvalidOperationException(); }
+ }
+
+ public void Add(KeyValuePair<TKey, TValue> item)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public void Clear()
+ {
+ throw new InvalidOperationException();
+ }
+
+ public bool Contains(KeyValuePair<TKey, TValue> item)
+ {
+ return wrapped.Contains(item);
+ }
+
+ public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
+ {
+ wrapped.CopyTo(array, arrayIndex);
+ }
+
+ public int Count
+ {
+ get { return wrapped.Count; }
+ }
+
+ public bool IsReadOnly
+ {
+ get { return true; }
+ }
+
+ public bool Remove(KeyValuePair<TKey, TValue> item)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
+ {
+ return wrapped.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable) wrapped).GetEnumerator();
+ }
+
+ public override bool Equals(object obj)
+ {
+ return wrapped.Equals(obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return wrapped.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return wrapped.ToString();
+ }
+ }
+}
\ No newline at end of file diff --git a/csharp/src/Google.Protobuf/Collections/RepeatedField.cs b/csharp/src/Google.Protobuf/Collections/RepeatedField.cs new file mode 100644 index 00000000..9bab41ea --- /dev/null +++ b/csharp/src/Google.Protobuf/Collections/RepeatedField.cs @@ -0,0 +1,470 @@ +#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; + +namespace Google.Protobuf.Collections +{ + /// <summary> + /// The contents of a repeated field: essentially, a collection with some extra + /// restrictions (no null values) and capabilities (deep cloning and freezing). + /// </summary> + /// <typeparam name="T">The element type of the repeated field.</typeparam> + public sealed class RepeatedField<T> : IList<T>, IList, IDeepCloneable<RepeatedField<T>>, IEquatable<RepeatedField<T>>, IFreezable + { + private static readonly T[] EmptyArray = new T[0]; + private const int MinArraySize = 8; + + private bool frozen; + private T[] array = EmptyArray; + private int count = 0; + + /// <summary> + /// Creates a deep clone of this repeated field. + /// </summary> + /// <remarks> + /// If the field type is + /// a message type, each element is also cloned; otherwise, it is + /// assumed that the field type is primitive (including string and + /// bytes, both of which are immutable) and so a simple copy is + /// equivalent to a deep clone. + /// </remarks> + /// <returns>A deep clone of this repeated field.</returns> + public RepeatedField<T> Clone() + { + RepeatedField<T> clone = new RepeatedField<T>(); + // Clone is implicitly *not* frozen, even if this object is. + if (array != EmptyArray) + { + clone.array = (T[])array.Clone(); + IDeepCloneable<T>[] cloneableArray = clone.array as IDeepCloneable<T>[]; + if (cloneableArray != null) + { + for (int i = 0; i < count; i++) + { + clone.array[i] = cloneableArray[i].Clone(); + } + } + } + clone.count = count; + return clone; + } + + public void AddEntriesFrom(CodedInputStream input, FieldCodec<T> codec) + { + // TODO: Inline some of the Add code, so we can avoid checking the size on every + // iteration and the mutability. + uint tag = input.LastTag; + var reader = codec.ValueReader; + // Value types can be packed or not. + if (typeof(T).IsValueType && WireFormat.GetTagWireType(tag) == WireFormat.WireType.LengthDelimited) + { + int length = input.ReadLength(); + if (length > 0) + { + int oldLimit = input.PushLimit(length); + while (!input.ReachedLimit) + { + Add(reader(input)); + } + input.PopLimit(oldLimit); + } + // Empty packed field. Odd, but valid - just ignore. + } + else + { + // Not packed... (possibly not packable) + do + { + Add(reader(input)); + } while (input.MaybeConsumeTag(tag)); + } + } + + public int CalculateSize(FieldCodec<T> codec) + { + if (count == 0) + { + return 0; + } + uint tag = codec.Tag; + if (typeof(T).IsValueType && WireFormat.GetTagWireType(tag) == WireFormat.WireType.LengthDelimited) + { + int dataSize = CalculatePackedDataSize(codec); + return CodedOutputStream.ComputeRawVarint32Size(tag) + + CodedOutputStream.ComputeLengthSize(dataSize) + + dataSize; + } + else + { + var sizeCalculator = codec.ValueSizeCalculator; + int size = count * CodedOutputStream.ComputeRawVarint32Size(tag); + for (int i = 0; i < count; i++) + { + size += sizeCalculator(array[i]); + } + return size; + } + } + + private int CalculatePackedDataSize(FieldCodec<T> codec) + { + int fixedSize = codec.FixedSize; + if (fixedSize == 0) + { + var calculator = codec.ValueSizeCalculator; + int tmp = 0; + for (int i = 0; i < count; i++) + { + tmp += calculator(array[i]); + } + return tmp; + } + else + { + return fixedSize * Count; + } + } + + public void WriteTo(CodedOutputStream output, FieldCodec<T> codec) + { + if (count == 0) + { + return; + } + var writer = codec.ValueWriter; + var tag = codec.Tag; + if (typeof(T).IsValueType && WireFormat.GetTagWireType(tag) == WireFormat.WireType.LengthDelimited) + { + // Packed primitive type + uint size = (uint)CalculatePackedDataSize(codec); + output.WriteTag(tag); + output.WriteRawVarint32(size); + for (int i = 0; i < count; i++) + { + writer(output, array[i]); + } + } + else + { + // Not packed: a simple tag/value pair for each value. + // Can't use codec.WriteTagAndValue, as that omits default values. + for (int i = 0; i < count; i++) + { + output.WriteTag(tag); + writer(output, array[i]); + } + } + } + + public bool IsFrozen { get { return frozen; } } + + public void Freeze() + { + frozen = true; + IFreezable[] freezableArray = array as IFreezable[]; + if (freezableArray != null) + { + for (int i = 0; i < count; i++) + { + freezableArray[i].Freeze(); + } + } + } + + private void EnsureSize(int size) + { + if (array.Length < size) + { + size = Math.Max(size, MinArraySize); + int newSize = Math.Max(array.Length * 2, size); + var tmp = new T[newSize]; + Array.Copy(array, 0, tmp, 0, array.Length); + array = tmp; + } + } + + public void Add(T item) + { + if (item == null) + { + throw new ArgumentNullException("item"); + } + this.CheckMutable(); + EnsureSize(count + 1); + array[count++] = item; + } + + public void Clear() + { + this.CheckMutable(); + array = EmptyArray; + count = 0; + } + + public bool Contains(T item) + { + return IndexOf(item) != -1; + } + + public void CopyTo(T[] array, int arrayIndex) + { + Array.Copy(this.array, 0, array, arrayIndex, count); + } + + public bool Remove(T item) + { + this.CheckMutable(); + int index = IndexOf(item); + if (index == -1) + { + return false; + } + Array.Copy(array, index + 1, array, index, count - index - 1); + count--; + array[count] = default(T); + return true; + } + + public int Count { get { return count; } } + + public bool IsReadOnly { get { return IsFrozen; } } + + public void Add(RepeatedField<T> values) + { + if (values == null) + { + throw new ArgumentNullException("values"); + } + this.CheckMutable(); + EnsureSize(count + values.count); + // We know that all the values will be valid, because it's a RepeatedField. + Array.Copy(values.array, 0, array, count, values.count); + count += values.count; + } + + public void Add(IEnumerable<T> values) + { + if (values == null) + { + throw new ArgumentNullException("values"); + } + this.CheckMutable(); + // TODO: Check for ICollection and get the Count? + foreach (T item in values) + { + Add(item); + } + } + + public IEnumerator<T> GetEnumerator() + { + for (int i = 0; i < count; i++) + { + yield return array[i]; + } + } + + public override bool Equals(object obj) + { + return Equals(obj as RepeatedField<T>); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public override int GetHashCode() + { + int hash = 0; + for (int i = 0; i < count; i++) + { + hash = hash * 31 + array[i].GetHashCode(); + } + return hash; + } + + public bool Equals(RepeatedField<T> other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + if (ReferenceEquals(other, this)) + { + return true; + } + if (other.Count != this.Count) + { + return false; + } + // TODO(jonskeet): Does this box for enums? + EqualityComparer<T> comparer = EqualityComparer<T>.Default; + for (int i = 0; i < count; i++) + { + if (!comparer.Equals(array[i], other.array[i])) + { + return false; + } + } + return true; + } + + public int IndexOf(T item) + { + if (item == null) + { + throw new ArgumentNullException("item"); + } + // TODO(jonskeet): Does this box for enums? + EqualityComparer<T> comparer = EqualityComparer<T>.Default; + for (int i = 0; i < count; i++) + { + if (comparer.Equals(array[i], item)) + { + return i; + } + } + return -1; + } + + public void Insert(int index, T item) + { + if (item == null) + { + throw new ArgumentNullException("item"); + } + if (index < 0 || index > count) + { + throw new ArgumentOutOfRangeException("index"); + } + this.CheckMutable(); + EnsureSize(count + 1); + Array.Copy(array, index, array, index + 1, count - index); + array[index] = item; + count++; + } + + public void RemoveAt(int index) + { + if (index < 0 || index >= count) + { + throw new ArgumentOutOfRangeException("index"); + } + this.CheckMutable(); + Array.Copy(array, index + 1, array, index, count - index - 1); + count--; + array[count] = default(T); + } + + public T this[int index] + { + get + { + if (index < 0 || index >= count) + { + throw new ArgumentOutOfRangeException("index"); + } + return array[index]; + } + set + { + if (index < 0 || index >= count) + { + throw new ArgumentOutOfRangeException("index"); + } + this.CheckMutable(); + if (value == null) + { + throw new ArgumentNullException("value"); + } + array[index] = value; + } + } + + #region Explicit interface implementation for IList and ICollection. + bool IList.IsFixedSize { get { return IsFrozen; } } + + void ICollection.CopyTo(Array array, int index) + { + Array.Copy(this.array, 0, array, index, count); + } + + bool ICollection.IsSynchronized { get { return false; } } + + object ICollection.SyncRoot { get { return this; } } + + object IList.this[int index] + { + get { return this[index]; } + set { this[index] = (T)value; } + } + + int IList.Add(object value) + { + Add((T) value); + return count - 1; + } + + bool IList.Contains(object value) + { + return (value is T && Contains((T)value)); + } + + int IList.IndexOf(object value) + { + if (!(value is T)) + { + return -1; + } + return IndexOf((T)value); + } + + void IList.Insert(int index, object value) + { + Insert(index, (T) value); + } + + void IList.Remove(object value) + { + if (!(value is T)) + { + return; + } + Remove((T)value); + } + #endregion + } +} |