#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.IO; namespace Google.Protobuf.Collections { /// /// The contents of a repeated field: essentially, a collection with some extra /// restrictions (no null values) and capabilities (deep cloning). /// /// /// This implementation does not generally prohibit the use of types which are not /// supported by Protocol Buffers but nor does it guarantee that all operations will work in such cases. /// /// The element type of the repeated field. public sealed class RepeatedField : IList, IList, IDeepCloneable>, IEquatable> #if !NET35 , IReadOnlyList #endif { private static readonly EqualityComparer EqualityComparer = ProtobufEqualityComparers.GetEqualityComparer(); private static readonly T[] EmptyArray = new T[0]; private const int MinArraySize = 8; private T[] array = EmptyArray; private int count = 0; /// /// Creates a deep clone of this repeated field. /// /// /// 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. /// /// A deep clone of this repeated field. public RepeatedField Clone() { RepeatedField clone = new RepeatedField(); if (array != EmptyArray) { clone.array = (T[])array.Clone(); IDeepCloneable[] cloneableArray = clone.array as IDeepCloneable[]; if (cloneableArray != null) { for (int i = 0; i < count; i++) { clone.array[i] = cloneableArray[i].Clone(); } } } clone.count = count; return clone; } /// /// Adds the entries from the given input stream, decoding them with the specified codec. /// /// The input stream to read from. /// The codec to use in order to read each entry. public void AddEntriesFrom(CodedInputStream input, FieldCodec codec) { // TODO: Inline some of the Add code, so we can avoid checking the size on every // iteration. uint tag = input.LastTag; var reader = codec.ValueReader; // Non-nullable value types can be packed or not. if (FieldCodec.IsPackedRepeatedField(tag)) { 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)); } } /// /// Calculates the size of this collection based on the given codec. /// /// The codec to use when encoding each field. /// The number of bytes that would be written to a by , /// using the same codec. public int CalculateSize(FieldCodec codec) { if (count == 0) { return 0; } uint tag = codec.Tag; if (codec.PackedRepeatedField) { 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 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; } } /// /// Writes the contents of this collection to the given , /// encoding each value using the specified codec. /// /// The output stream to write to. /// The codec to use when encoding each value. public void WriteTo(CodedOutputStream output, FieldCodec codec) { if (count == 0) { return; } var writer = codec.ValueWriter; var tag = codec.Tag; if (codec.PackedRepeatedField) { // 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]); } } } 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; } } /// /// Adds the specified item to the collection. /// /// The item to add. public void Add(T item) { ProtoPreconditions.CheckNotNullUnconstrained(item, nameof(item)); EnsureSize(count + 1); array[count++] = item; } /// /// Removes all items from the collection. /// public void Clear() { array = EmptyArray; count = 0; } /// /// Determines whether this collection contains the given item. /// /// The item to find. /// true if this collection contains the given item; false otherwise. public bool Contains(T item) { return IndexOf(item) != -1; } /// /// Copies this collection to the given array. /// /// The array to copy to. /// The first index of the array to copy to. public void CopyTo(T[] array, int arrayIndex) { Array.Copy(this.array, 0, array, arrayIndex, count); } /// /// Removes the specified item from the collection /// /// The item to remove. /// true if the item was found and removed; false otherwise. public bool Remove(T item) { 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; } /// /// Gets the number of elements contained in the collection. /// public int Count => count; /// /// Gets a value indicating whether the collection is read-only. /// public bool IsReadOnly => false; /// /// Adds all of the specified values into this collection. /// /// The values to add to this collection. public void AddRange(IEnumerable values) { ProtoPreconditions.CheckNotNull(values, nameof(values)); // Optimization 1: If the collection we're adding is already a RepeatedField, // we know the values are valid. var otherRepeatedField = values as RepeatedField; if (otherRepeatedField != null) { EnsureSize(count + otherRepeatedField.count); Array.Copy(otherRepeatedField.array, 0, array, count, otherRepeatedField.count); count += otherRepeatedField.count; return; } // Optimization 2: The collection is an ICollection, so we can expand // just once and ask the collection to copy itself into the array. var collection = values as ICollection; if (collection != null) { var extraCount = collection.Count; // For reference types and nullable value types, we need to check that there are no nulls // present. (This isn't a thread-safe approach, but we don't advertise this is thread-safe.) // We expect the JITter to optimize this test to true/false, so it's effectively conditional // specialization. if (default(T) == null) { // TODO: Measure whether iterating once to check and then letting the collection copy // itself is faster or slower than iterating and adding as we go. For large // collections this will not be great in terms of cache usage... but the optimized // copy may be significantly faster than doing it one at a time. foreach (var item in collection) { if (item == null) { throw new ArgumentException("Sequence contained null element", nameof(values)); } } } EnsureSize(count + extraCount); collection.CopyTo(array, count); count += extraCount; return; } // We *could* check for ICollection as well, but very very few collections implement // ICollection but not ICollection. (HashSet does, for one...) // Fall back to a slower path of adding items one at a time. foreach (T item in values) { Add(item); } } /// /// Adds all of the specified values into this collection. This method is present to /// allow repeated fields to be constructed from queries within collection initializers. /// Within non-collection-initializer code, consider using the equivalent /// method instead for clarity. /// /// The values to add to this collection. public void Add(IEnumerable values) { AddRange(values); } /// /// Returns an enumerator that iterates through the collection. /// /// /// An enumerator that can be used to iterate through the collection. /// public IEnumerator GetEnumerator() { for (int i = 0; i < count; i++) { yield return array[i]; } } /// /// Determines whether the specified , is equal to this instance. /// /// The to compare with this instance. /// /// true if the specified is equal to this instance; otherwise, false. /// public override bool Equals(object obj) { return Equals(obj as RepeatedField); } /// /// Returns an enumerator that iterates through a collection. /// /// /// An object that can be used to iterate through the collection. /// IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Returns a hash code for this instance. /// /// /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// public override int GetHashCode() { int hash = 0; for (int i = 0; i < count; i++) { hash = hash * 31 + array[i].GetHashCode(); } return hash; } /// /// Compares this repeated field with another for equality. /// /// The repeated field to compare this with. /// true if refers to an equal repeated field; false otherwise. public bool Equals(RepeatedField other) { if (ReferenceEquals(other, null)) { return false; } if (ReferenceEquals(other, this)) { return true; } if (other.Count != this.Count) { return false; } EqualityComparer comparer = EqualityComparer; for (int i = 0; i < count; i++) { if (!comparer.Equals(array[i], other.array[i])) { return false; } } return true; } /// /// Returns the index of the given item within the collection, or -1 if the item is not /// present. /// /// The item to find in the collection. /// The zero-based index of the item, or -1 if it is not found. public int IndexOf(T item) { ProtoPreconditions.CheckNotNullUnconstrained(item, nameof(item)); EqualityComparer comparer = EqualityComparer; for (int i = 0; i < count; i++) { if (comparer.Equals(array[i], item)) { return i; } } return -1; } /// /// Inserts the given item at the specified index. /// /// The index at which to insert the item. /// The item to insert. public void Insert(int index, T item) { ProtoPreconditions.CheckNotNullUnconstrained(item, nameof(item)); if (index < 0 || index > count) { throw new ArgumentOutOfRangeException(nameof(index)); } EnsureSize(count + 1); Array.Copy(array, index, array, index + 1, count - index); array[index] = item; count++; } /// /// Removes the item at the given index. /// /// The zero-based index of the item to remove. public void RemoveAt(int index) { if (index < 0 || index >= count) { throw new ArgumentOutOfRangeException(nameof(index)); } Array.Copy(array, index + 1, array, index, count - index - 1); count--; array[count] = default(T); } /// /// Returns a string representation of this repeated field, in the same /// way as it would be represented by the default JSON formatter. /// public override string ToString() { var writer = new StringWriter(); JsonFormatter.Default.WriteList(writer, this); return writer.ToString(); } /// /// Gets or sets the item at the specified index. /// /// /// The element at the specified index. /// /// The zero-based index of the element to get or set. /// The item at the specified index. public T this[int index] { get { if (index < 0 || index >= count) { throw new ArgumentOutOfRangeException(nameof(index)); } return array[index]; } set { if (index < 0 || index >= count) { throw new ArgumentOutOfRangeException(nameof(index)); } ProtoPreconditions.CheckNotNullUnconstrained(value, nameof(value)); array[index] = value; } } #region Explicit interface implementation for IList and ICollection. bool IList.IsFixedSize => false; void ICollection.CopyTo(Array array, int index) { Array.Copy(this.array, 0, array, index, count); } bool ICollection.IsSynchronized => false; object ICollection.SyncRoot => 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 } }