blob: c18b63e23f823d39b493547c982da39b12c45a27 [file] [log] [blame]
Brian Silverman9c614bc2016-02-15 20:20:02 -05001#region Copyright notice and license
2// Protocol Buffers - Google's data interchange format
3// Copyright 2015 Google Inc. All rights reserved.
4// https://developers.google.com/protocol-buffers/
5//
6// Redistribution and use in source and binary forms, with or without
7// modification, are permitted provided that the following conditions are
8// met:
9//
10// * Redistributions of source code must retain the above copyright
11// notice, this list of conditions and the following disclaimer.
12// * Redistributions in binary form must reproduce the above
13// copyright notice, this list of conditions and the following disclaimer
14// in the documentation and/or other materials provided with the
15// distribution.
16// * Neither the name of Google Inc. nor the names of its
17// contributors may be used to endorse or promote products derived from
18// this software without specific prior written permission.
19//
20// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31#endregion
32
33using System;
34using System.Collections;
35using System.Collections.Generic;
Austin Schuh40c16522018-10-28 20:27:54 -070036using System.IO;
Brian Silverman9c614bc2016-02-15 20:20:02 -050037
38namespace Google.Protobuf.Collections
39{
40 /// <summary>
41 /// The contents of a repeated field: essentially, a collection with some extra
42 /// restrictions (no null values) and capabilities (deep cloning).
43 /// </summary>
44 /// <remarks>
45 /// This implementation does not generally prohibit the use of types which are not
46 /// supported by Protocol Buffers but nor does it guarantee that all operations will work in such cases.
47 /// </remarks>
48 /// <typeparam name="T">The element type of the repeated field.</typeparam>
49 public sealed class RepeatedField<T> : IList<T>, IList, IDeepCloneable<RepeatedField<T>>, IEquatable<RepeatedField<T>>
Austin Schuh40c16522018-10-28 20:27:54 -070050#if !NET35
51 , IReadOnlyList<T>
52#endif
Brian Silverman9c614bc2016-02-15 20:20:02 -050053 {
Austin Schuh40c16522018-10-28 20:27:54 -070054 private static readonly EqualityComparer<T> EqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<T>();
Brian Silverman9c614bc2016-02-15 20:20:02 -050055 private static readonly T[] EmptyArray = new T[0];
56 private const int MinArraySize = 8;
57
58 private T[] array = EmptyArray;
59 private int count = 0;
60
61 /// <summary>
62 /// Creates a deep clone of this repeated field.
63 /// </summary>
64 /// <remarks>
65 /// If the field type is
66 /// a message type, each element is also cloned; otherwise, it is
67 /// assumed that the field type is primitive (including string and
68 /// bytes, both of which are immutable) and so a simple copy is
69 /// equivalent to a deep clone.
70 /// </remarks>
71 /// <returns>A deep clone of this repeated field.</returns>
72 public RepeatedField<T> Clone()
73 {
74 RepeatedField<T> clone = new RepeatedField<T>();
75 if (array != EmptyArray)
76 {
77 clone.array = (T[])array.Clone();
78 IDeepCloneable<T>[] cloneableArray = clone.array as IDeepCloneable<T>[];
79 if (cloneableArray != null)
80 {
81 for (int i = 0; i < count; i++)
82 {
83 clone.array[i] = cloneableArray[i].Clone();
84 }
85 }
86 }
87 clone.count = count;
88 return clone;
89 }
90
91 /// <summary>
92 /// Adds the entries from the given input stream, decoding them with the specified codec.
93 /// </summary>
94 /// <param name="input">The input stream to read from.</param>
95 /// <param name="codec">The codec to use in order to read each entry.</param>
96 public void AddEntriesFrom(CodedInputStream input, FieldCodec<T> codec)
97 {
98 // TODO: Inline some of the Add code, so we can avoid checking the size on every
99 // iteration.
100 uint tag = input.LastTag;
101 var reader = codec.ValueReader;
102 // Non-nullable value types can be packed or not.
103 if (FieldCodec<T>.IsPackedRepeatedField(tag))
104 {
105 int length = input.ReadLength();
106 if (length > 0)
107 {
108 int oldLimit = input.PushLimit(length);
109 while (!input.ReachedLimit)
110 {
111 Add(reader(input));
112 }
113 input.PopLimit(oldLimit);
114 }
115 // Empty packed field. Odd, but valid - just ignore.
116 }
117 else
118 {
119 // Not packed... (possibly not packable)
120 do
121 {
122 Add(reader(input));
123 } while (input.MaybeConsumeTag(tag));
124 }
125 }
126
127 /// <summary>
128 /// Calculates the size of this collection based on the given codec.
129 /// </summary>
130 /// <param name="codec">The codec to use when encoding each field.</param>
131 /// <returns>The number of bytes that would be written to a <see cref="CodedOutputStream"/> by <see cref="WriteTo"/>,
132 /// using the same codec.</returns>
133 public int CalculateSize(FieldCodec<T> codec)
134 {
135 if (count == 0)
136 {
137 return 0;
138 }
139 uint tag = codec.Tag;
140 if (codec.PackedRepeatedField)
141 {
142 int dataSize = CalculatePackedDataSize(codec);
143 return CodedOutputStream.ComputeRawVarint32Size(tag) +
144 CodedOutputStream.ComputeLengthSize(dataSize) +
145 dataSize;
146 }
147 else
148 {
149 var sizeCalculator = codec.ValueSizeCalculator;
150 int size = count * CodedOutputStream.ComputeRawVarint32Size(tag);
151 for (int i = 0; i < count; i++)
152 {
153 size += sizeCalculator(array[i]);
154 }
155 return size;
156 }
157 }
158
159 private int CalculatePackedDataSize(FieldCodec<T> codec)
160 {
161 int fixedSize = codec.FixedSize;
162 if (fixedSize == 0)
163 {
164 var calculator = codec.ValueSizeCalculator;
165 int tmp = 0;
166 for (int i = 0; i < count; i++)
167 {
168 tmp += calculator(array[i]);
169 }
170 return tmp;
171 }
172 else
173 {
174 return fixedSize * Count;
175 }
176 }
177
178 /// <summary>
179 /// Writes the contents of this collection to the given <see cref="CodedOutputStream"/>,
180 /// encoding each value using the specified codec.
181 /// </summary>
182 /// <param name="output">The output stream to write to.</param>
183 /// <param name="codec">The codec to use when encoding each value.</param>
184 public void WriteTo(CodedOutputStream output, FieldCodec<T> codec)
185 {
186 if (count == 0)
187 {
188 return;
189 }
190 var writer = codec.ValueWriter;
191 var tag = codec.Tag;
192 if (codec.PackedRepeatedField)
193 {
194 // Packed primitive type
195 uint size = (uint)CalculatePackedDataSize(codec);
196 output.WriteTag(tag);
197 output.WriteRawVarint32(size);
198 for (int i = 0; i < count; i++)
199 {
200 writer(output, array[i]);
201 }
202 }
203 else
204 {
205 // Not packed: a simple tag/value pair for each value.
206 // Can't use codec.WriteTagAndValue, as that omits default values.
207 for (int i = 0; i < count; i++)
208 {
209 output.WriteTag(tag);
210 writer(output, array[i]);
211 }
212 }
213 }
214
215 private void EnsureSize(int size)
216 {
217 if (array.Length < size)
218 {
219 size = Math.Max(size, MinArraySize);
220 int newSize = Math.Max(array.Length * 2, size);
221 var tmp = new T[newSize];
222 Array.Copy(array, 0, tmp, 0, array.Length);
223 array = tmp;
224 }
225 }
226
227 /// <summary>
228 /// Adds the specified item to the collection.
229 /// </summary>
230 /// <param name="item">The item to add.</param>
231 public void Add(T item)
232 {
Austin Schuh40c16522018-10-28 20:27:54 -0700233 ProtoPreconditions.CheckNotNullUnconstrained(item, nameof(item));
Brian Silverman9c614bc2016-02-15 20:20:02 -0500234 EnsureSize(count + 1);
235 array[count++] = item;
236 }
237
238 /// <summary>
239 /// Removes all items from the collection.
240 /// </summary>
241 public void Clear()
242 {
243 array = EmptyArray;
244 count = 0;
245 }
246
247 /// <summary>
248 /// Determines whether this collection contains the given item.
249 /// </summary>
250 /// <param name="item">The item to find.</param>
251 /// <returns><c>true</c> if this collection contains the given item; <c>false</c> otherwise.</returns>
252 public bool Contains(T item)
253 {
254 return IndexOf(item) != -1;
255 }
256
257 /// <summary>
258 /// Copies this collection to the given array.
259 /// </summary>
260 /// <param name="array">The array to copy to.</param>
261 /// <param name="arrayIndex">The first index of the array to copy to.</param>
262 public void CopyTo(T[] array, int arrayIndex)
263 {
264 Array.Copy(this.array, 0, array, arrayIndex, count);
265 }
266
267 /// <summary>
268 /// Removes the specified item from the collection
269 /// </summary>
270 /// <param name="item">The item to remove.</param>
271 /// <returns><c>true</c> if the item was found and removed; <c>false</c> otherwise.</returns>
272 public bool Remove(T item)
273 {
274 int index = IndexOf(item);
275 if (index == -1)
276 {
277 return false;
278 }
279 Array.Copy(array, index + 1, array, index, count - index - 1);
280 count--;
281 array[count] = default(T);
282 return true;
283 }
284
285 /// <summary>
286 /// Gets the number of elements contained in the collection.
287 /// </summary>
Austin Schuh40c16522018-10-28 20:27:54 -0700288 public int Count => count;
Brian Silverman9c614bc2016-02-15 20:20:02 -0500289
290 /// <summary>
291 /// Gets a value indicating whether the collection is read-only.
292 /// </summary>
Austin Schuh40c16522018-10-28 20:27:54 -0700293 public bool IsReadOnly => false;
Brian Silverman9c614bc2016-02-15 20:20:02 -0500294
295 /// <summary>
296 /// Adds all of the specified values into this collection.
297 /// </summary>
298 /// <param name="values">The values to add to this collection.</param>
Austin Schuh40c16522018-10-28 20:27:54 -0700299 public void AddRange(IEnumerable<T> values)
Brian Silverman9c614bc2016-02-15 20:20:02 -0500300 {
Austin Schuh40c16522018-10-28 20:27:54 -0700301 ProtoPreconditions.CheckNotNull(values, nameof(values));
Brian Silverman9c614bc2016-02-15 20:20:02 -0500302
Austin Schuh40c16522018-10-28 20:27:54 -0700303 // Optimization 1: If the collection we're adding is already a RepeatedField<T>,
304 // we know the values are valid.
305 var otherRepeatedField = values as RepeatedField<T>;
306 if (otherRepeatedField != null)
Brian Silverman9c614bc2016-02-15 20:20:02 -0500307 {
Austin Schuh40c16522018-10-28 20:27:54 -0700308 EnsureSize(count + otherRepeatedField.count);
309 Array.Copy(otherRepeatedField.array, 0, array, count, otherRepeatedField.count);
310 count += otherRepeatedField.count;
311 return;
Brian Silverman9c614bc2016-02-15 20:20:02 -0500312 }
Austin Schuh40c16522018-10-28 20:27:54 -0700313
314 // Optimization 2: The collection is an ICollection, so we can expand
315 // just once and ask the collection to copy itself into the array.
316 var collection = values as ICollection;
317 if (collection != null)
318 {
319 var extraCount = collection.Count;
320 // For reference types and nullable value types, we need to check that there are no nulls
321 // present. (This isn't a thread-safe approach, but we don't advertise this is thread-safe.)
322 // We expect the JITter to optimize this test to true/false, so it's effectively conditional
323 // specialization.
324 if (default(T) == null)
325 {
326 // TODO: Measure whether iterating once to check and then letting the collection copy
327 // itself is faster or slower than iterating and adding as we go. For large
328 // collections this will not be great in terms of cache usage... but the optimized
329 // copy may be significantly faster than doing it one at a time.
330 foreach (var item in collection)
331 {
332 if (item == null)
333 {
334 throw new ArgumentException("Sequence contained null element", nameof(values));
335 }
336 }
337 }
338 EnsureSize(count + extraCount);
339 collection.CopyTo(array, count);
340 count += extraCount;
341 return;
342 }
343
344 // We *could* check for ICollection<T> as well, but very very few collections implement
345 // ICollection<T> but not ICollection. (HashSet<T> does, for one...)
346
347 // Fall back to a slower path of adding items one at a time.
Brian Silverman9c614bc2016-02-15 20:20:02 -0500348 foreach (T item in values)
349 {
350 Add(item);
351 }
352 }
353
354 /// <summary>
Austin Schuh40c16522018-10-28 20:27:54 -0700355 /// Adds all of the specified values into this collection. This method is present to
356 /// allow repeated fields to be constructed from queries within collection initializers.
357 /// Within non-collection-initializer code, consider using the equivalent <see cref="AddRange"/>
358 /// method instead for clarity.
359 /// </summary>
360 /// <param name="values">The values to add to this collection.</param>
361 public void Add(IEnumerable<T> values)
362 {
363 AddRange(values);
364 }
365
366 /// <summary>
Brian Silverman9c614bc2016-02-15 20:20:02 -0500367 /// Returns an enumerator that iterates through the collection.
368 /// </summary>
369 /// <returns>
370 /// An enumerator that can be used to iterate through the collection.
371 /// </returns>
372 public IEnumerator<T> GetEnumerator()
373 {
374 for (int i = 0; i < count; i++)
375 {
376 yield return array[i];
377 }
378 }
379
380 /// <summary>
381 /// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
382 /// </summary>
383 /// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
384 /// <returns>
385 /// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
386 /// </returns>
387 public override bool Equals(object obj)
388 {
389 return Equals(obj as RepeatedField<T>);
390 }
391
392 /// <summary>
393 /// Returns an enumerator that iterates through a collection.
394 /// </summary>
395 /// <returns>
396 /// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
397 /// </returns>
398 IEnumerator IEnumerable.GetEnumerator()
399 {
400 return GetEnumerator();
401 }
402
403 /// <summary>
404 /// Returns a hash code for this instance.
405 /// </summary>
406 /// <returns>
407 /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
408 /// </returns>
409 public override int GetHashCode()
410 {
411 int hash = 0;
412 for (int i = 0; i < count; i++)
413 {
414 hash = hash * 31 + array[i].GetHashCode();
415 }
416 return hash;
417 }
418
419 /// <summary>
420 /// Compares this repeated field with another for equality.
421 /// </summary>
422 /// <param name="other">The repeated field to compare this with.</param>
423 /// <returns><c>true</c> if <paramref name="other"/> refers to an equal repeated field; <c>false</c> otherwise.</returns>
424 public bool Equals(RepeatedField<T> other)
425 {
426 if (ReferenceEquals(other, null))
427 {
428 return false;
429 }
430 if (ReferenceEquals(other, this))
431 {
432 return true;
433 }
434 if (other.Count != this.Count)
435 {
436 return false;
437 }
Austin Schuh40c16522018-10-28 20:27:54 -0700438 EqualityComparer<T> comparer = EqualityComparer;
Brian Silverman9c614bc2016-02-15 20:20:02 -0500439 for (int i = 0; i < count; i++)
440 {
441 if (!comparer.Equals(array[i], other.array[i]))
442 {
443 return false;
444 }
445 }
446 return true;
447 }
448
449 /// <summary>
450 /// Returns the index of the given item within the collection, or -1 if the item is not
451 /// present.
452 /// </summary>
453 /// <param name="item">The item to find in the collection.</param>
454 /// <returns>The zero-based index of the item, or -1 if it is not found.</returns>
455 public int IndexOf(T item)
456 {
Austin Schuh40c16522018-10-28 20:27:54 -0700457 ProtoPreconditions.CheckNotNullUnconstrained(item, nameof(item));
458 EqualityComparer<T> comparer = EqualityComparer;
Brian Silverman9c614bc2016-02-15 20:20:02 -0500459 for (int i = 0; i < count; i++)
460 {
461 if (comparer.Equals(array[i], item))
462 {
463 return i;
464 }
465 }
466 return -1;
467 }
468
469 /// <summary>
470 /// Inserts the given item at the specified index.
471 /// </summary>
472 /// <param name="index">The index at which to insert the item.</param>
473 /// <param name="item">The item to insert.</param>
474 public void Insert(int index, T item)
475 {
Austin Schuh40c16522018-10-28 20:27:54 -0700476 ProtoPreconditions.CheckNotNullUnconstrained(item, nameof(item));
Brian Silverman9c614bc2016-02-15 20:20:02 -0500477 if (index < 0 || index > count)
478 {
Austin Schuh40c16522018-10-28 20:27:54 -0700479 throw new ArgumentOutOfRangeException(nameof(index));
Brian Silverman9c614bc2016-02-15 20:20:02 -0500480 }
481 EnsureSize(count + 1);
482 Array.Copy(array, index, array, index + 1, count - index);
483 array[index] = item;
484 count++;
485 }
486
487 /// <summary>
488 /// Removes the item at the given index.
489 /// </summary>
490 /// <param name="index">The zero-based index of the item to remove.</param>
491 public void RemoveAt(int index)
492 {
493 if (index < 0 || index >= count)
494 {
Austin Schuh40c16522018-10-28 20:27:54 -0700495 throw new ArgumentOutOfRangeException(nameof(index));
Brian Silverman9c614bc2016-02-15 20:20:02 -0500496 }
497 Array.Copy(array, index + 1, array, index, count - index - 1);
498 count--;
499 array[count] = default(T);
500 }
501
502 /// <summary>
503 /// Returns a string representation of this repeated field, in the same
504 /// way as it would be represented by the default JSON formatter.
505 /// </summary>
506 public override string ToString()
507 {
Austin Schuh40c16522018-10-28 20:27:54 -0700508 var writer = new StringWriter();
509 JsonFormatter.Default.WriteList(writer, this);
510 return writer.ToString();
Brian Silverman9c614bc2016-02-15 20:20:02 -0500511 }
512
513 /// <summary>
514 /// Gets or sets the item at the specified index.
515 /// </summary>
516 /// <value>
517 /// The element at the specified index.
518 /// </value>
519 /// <param name="index">The zero-based index of the element to get or set.</param>
520 /// <returns>The item at the specified index.</returns>
521 public T this[int index]
522 {
523 get
524 {
525 if (index < 0 || index >= count)
526 {
Austin Schuh40c16522018-10-28 20:27:54 -0700527 throw new ArgumentOutOfRangeException(nameof(index));
Brian Silverman9c614bc2016-02-15 20:20:02 -0500528 }
529 return array[index];
530 }
531 set
532 {
533 if (index < 0 || index >= count)
534 {
Austin Schuh40c16522018-10-28 20:27:54 -0700535 throw new ArgumentOutOfRangeException(nameof(index));
Brian Silverman9c614bc2016-02-15 20:20:02 -0500536 }
Austin Schuh40c16522018-10-28 20:27:54 -0700537 ProtoPreconditions.CheckNotNullUnconstrained(value, nameof(value));
Brian Silverman9c614bc2016-02-15 20:20:02 -0500538 array[index] = value;
539 }
540 }
541
542 #region Explicit interface implementation for IList and ICollection.
Austin Schuh40c16522018-10-28 20:27:54 -0700543 bool IList.IsFixedSize => false;
Brian Silverman9c614bc2016-02-15 20:20:02 -0500544
545 void ICollection.CopyTo(Array array, int index)
546 {
547 Array.Copy(this.array, 0, array, index, count);
548 }
549
Austin Schuh40c16522018-10-28 20:27:54 -0700550 bool ICollection.IsSynchronized => false;
Brian Silverman9c614bc2016-02-15 20:20:02 -0500551
Austin Schuh40c16522018-10-28 20:27:54 -0700552 object ICollection.SyncRoot => this;
Brian Silverman9c614bc2016-02-15 20:20:02 -0500553
554 object IList.this[int index]
555 {
556 get { return this[index]; }
557 set { this[index] = (T)value; }
558 }
559
560 int IList.Add(object value)
561 {
562 Add((T) value);
563 return count - 1;
564 }
565
566 bool IList.Contains(object value)
567 {
568 return (value is T && Contains((T)value));
569 }
570
571 int IList.IndexOf(object value)
572 {
573 if (!(value is T))
574 {
575 return -1;
576 }
577 return IndexOf((T)value);
578 }
579
580 void IList.Insert(int index, object value)
581 {
582 Insert(index, (T) value);
583 }
584
585 void IList.Remove(object value)
586 {
587 if (!(value is T))
588 {
589 return;
590 }
591 Remove((T)value);
592 }
593 #endregion
594 }
595}