using System; using System.Collections; using System.Collections.Generic; namespace Google.Protobuf.Collections { /// /// The contents of a repeated field: essentially, a collection with some extra /// restrictions (no null values) and capabilities (deep cloning and freezing). /// /// The element type of the repeated field. public sealed class RepeatedField : IList, IDeepCloneable>, IEquatable>, IFreezable { private static readonly T[] EmptyArray = new T[0]; private bool frozen; 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(); // Clone is implicitly *not* frozen, even if this object is. 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; } public void AddEntriesFrom(CodedInputStream input, FieldCodec 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 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.ComputeRawVarint32Size((uint)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; } } public void WriteTo(CodedOutputStream output, FieldCodec 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; } } // TODO(jonskeet): If we implement freezing, make this reflect it. public bool IsReadOnly { get { return IsFrozen; } } public void Add(RepeatedField 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 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 RepeatedField.Enumerator GetEnumerator() { return new Enumerator(this); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public override bool Equals(object obj) { return Equals(obj as RepeatedField); } 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 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 comparer = EqualityComparer.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 comparer = EqualityComparer.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); 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; } } public struct Enumerator : IEnumerator { private int index; private readonly RepeatedField field; public Enumerator(RepeatedField field) { this.field = field; this.index = -1; } public bool MoveNext() { if (index + 1 >= field.Count) { return false; } index++; return true; } public void Reset() { index = -1; } public T Current { get { if (index == -1 || index >= field.count) { throw new InvalidOperationException(); } return field.array[index]; } } object IEnumerator.Current { get { return Current; } } public void Dispose() { } } } }