blob: 1cde03bc72741a14a654f7e64c04d5c4b7009a17 [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;
36using System.Text;
37
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>>
50 {
51 private static readonly T[] EmptyArray = new T[0];
52 private const int MinArraySize = 8;
53
54 private T[] array = EmptyArray;
55 private int count = 0;
56
57 /// <summary>
58 /// Creates a deep clone of this repeated field.
59 /// </summary>
60 /// <remarks>
61 /// If the field type is
62 /// a message type, each element is also cloned; otherwise, it is
63 /// assumed that the field type is primitive (including string and
64 /// bytes, both of which are immutable) and so a simple copy is
65 /// equivalent to a deep clone.
66 /// </remarks>
67 /// <returns>A deep clone of this repeated field.</returns>
68 public RepeatedField<T> Clone()
69 {
70 RepeatedField<T> clone = new RepeatedField<T>();
71 if (array != EmptyArray)
72 {
73 clone.array = (T[])array.Clone();
74 IDeepCloneable<T>[] cloneableArray = clone.array as IDeepCloneable<T>[];
75 if (cloneableArray != null)
76 {
77 for (int i = 0; i < count; i++)
78 {
79 clone.array[i] = cloneableArray[i].Clone();
80 }
81 }
82 }
83 clone.count = count;
84 return clone;
85 }
86
87 /// <summary>
88 /// Adds the entries from the given input stream, decoding them with the specified codec.
89 /// </summary>
90 /// <param name="input">The input stream to read from.</param>
91 /// <param name="codec">The codec to use in order to read each entry.</param>
92 public void AddEntriesFrom(CodedInputStream input, FieldCodec<T> codec)
93 {
94 // TODO: Inline some of the Add code, so we can avoid checking the size on every
95 // iteration.
96 uint tag = input.LastTag;
97 var reader = codec.ValueReader;
98 // Non-nullable value types can be packed or not.
99 if (FieldCodec<T>.IsPackedRepeatedField(tag))
100 {
101 int length = input.ReadLength();
102 if (length > 0)
103 {
104 int oldLimit = input.PushLimit(length);
105 while (!input.ReachedLimit)
106 {
107 Add(reader(input));
108 }
109 input.PopLimit(oldLimit);
110 }
111 // Empty packed field. Odd, but valid - just ignore.
112 }
113 else
114 {
115 // Not packed... (possibly not packable)
116 do
117 {
118 Add(reader(input));
119 } while (input.MaybeConsumeTag(tag));
120 }
121 }
122
123 /// <summary>
124 /// Calculates the size of this collection based on the given codec.
125 /// </summary>
126 /// <param name="codec">The codec to use when encoding each field.</param>
127 /// <returns>The number of bytes that would be written to a <see cref="CodedOutputStream"/> by <see cref="WriteTo"/>,
128 /// using the same codec.</returns>
129 public int CalculateSize(FieldCodec<T> codec)
130 {
131 if (count == 0)
132 {
133 return 0;
134 }
135 uint tag = codec.Tag;
136 if (codec.PackedRepeatedField)
137 {
138 int dataSize = CalculatePackedDataSize(codec);
139 return CodedOutputStream.ComputeRawVarint32Size(tag) +
140 CodedOutputStream.ComputeLengthSize(dataSize) +
141 dataSize;
142 }
143 else
144 {
145 var sizeCalculator = codec.ValueSizeCalculator;
146 int size = count * CodedOutputStream.ComputeRawVarint32Size(tag);
147 for (int i = 0; i < count; i++)
148 {
149 size += sizeCalculator(array[i]);
150 }
151 return size;
152 }
153 }
154
155 private int CalculatePackedDataSize(FieldCodec<T> codec)
156 {
157 int fixedSize = codec.FixedSize;
158 if (fixedSize == 0)
159 {
160 var calculator = codec.ValueSizeCalculator;
161 int tmp = 0;
162 for (int i = 0; i < count; i++)
163 {
164 tmp += calculator(array[i]);
165 }
166 return tmp;
167 }
168 else
169 {
170 return fixedSize * Count;
171 }
172 }
173
174 /// <summary>
175 /// Writes the contents of this collection to the given <see cref="CodedOutputStream"/>,
176 /// encoding each value using the specified codec.
177 /// </summary>
178 /// <param name="output">The output stream to write to.</param>
179 /// <param name="codec">The codec to use when encoding each value.</param>
180 public void WriteTo(CodedOutputStream output, FieldCodec<T> codec)
181 {
182 if (count == 0)
183 {
184 return;
185 }
186 var writer = codec.ValueWriter;
187 var tag = codec.Tag;
188 if (codec.PackedRepeatedField)
189 {
190 // Packed primitive type
191 uint size = (uint)CalculatePackedDataSize(codec);
192 output.WriteTag(tag);
193 output.WriteRawVarint32(size);
194 for (int i = 0; i < count; i++)
195 {
196 writer(output, array[i]);
197 }
198 }
199 else
200 {
201 // Not packed: a simple tag/value pair for each value.
202 // Can't use codec.WriteTagAndValue, as that omits default values.
203 for (int i = 0; i < count; i++)
204 {
205 output.WriteTag(tag);
206 writer(output, array[i]);
207 }
208 }
209 }
210
211 private void EnsureSize(int size)
212 {
213 if (array.Length < size)
214 {
215 size = Math.Max(size, MinArraySize);
216 int newSize = Math.Max(array.Length * 2, size);
217 var tmp = new T[newSize];
218 Array.Copy(array, 0, tmp, 0, array.Length);
219 array = tmp;
220 }
221 }
222
223 /// <summary>
224 /// Adds the specified item to the collection.
225 /// </summary>
226 /// <param name="item">The item to add.</param>
227 public void Add(T item)
228 {
229 if (item == null)
230 {
231 throw new ArgumentNullException("item");
232 }
233 EnsureSize(count + 1);
234 array[count++] = item;
235 }
236
237 /// <summary>
238 /// Removes all items from the collection.
239 /// </summary>
240 public void Clear()
241 {
242 array = EmptyArray;
243 count = 0;
244 }
245
246 /// <summary>
247 /// Determines whether this collection contains the given item.
248 /// </summary>
249 /// <param name="item">The item to find.</param>
250 /// <returns><c>true</c> if this collection contains the given item; <c>false</c> otherwise.</returns>
251 public bool Contains(T item)
252 {
253 return IndexOf(item) != -1;
254 }
255
256 /// <summary>
257 /// Copies this collection to the given array.
258 /// </summary>
259 /// <param name="array">The array to copy to.</param>
260 /// <param name="arrayIndex">The first index of the array to copy to.</param>
261 public void CopyTo(T[] array, int arrayIndex)
262 {
263 Array.Copy(this.array, 0, array, arrayIndex, count);
264 }
265
266 /// <summary>
267 /// Removes the specified item from the collection
268 /// </summary>
269 /// <param name="item">The item to remove.</param>
270 /// <returns><c>true</c> if the item was found and removed; <c>false</c> otherwise.</returns>
271 public bool Remove(T item)
272 {
273 int index = IndexOf(item);
274 if (index == -1)
275 {
276 return false;
277 }
278 Array.Copy(array, index + 1, array, index, count - index - 1);
279 count--;
280 array[count] = default(T);
281 return true;
282 }
283
284 /// <summary>
285 /// Gets the number of elements contained in the collection.
286 /// </summary>
287 public int Count { get { return count; } }
288
289 /// <summary>
290 /// Gets a value indicating whether the collection is read-only.
291 /// </summary>
292 public bool IsReadOnly { get { return false; } }
293
294 // TODO: Remove this overload and just handle it in the one below, at execution time?
295
296 /// <summary>
297 /// Adds all of the specified values into this collection.
298 /// </summary>
299 /// <param name="values">The values to add to this collection.</param>
300 public void Add(RepeatedField<T> values)
301 {
302 if (values == null)
303 {
304 throw new ArgumentNullException("values");
305 }
306 EnsureSize(count + values.count);
307 // We know that all the values will be valid, because it's a RepeatedField.
308 Array.Copy(values.array, 0, array, count, values.count);
309 count += values.count;
310 }
311
312 /// <summary>
313 /// Adds all of the specified values into this collection.
314 /// </summary>
315 /// <param name="values">The values to add to this collection.</param>
316 public void Add(IEnumerable<T> values)
317 {
318 if (values == null)
319 {
320 throw new ArgumentNullException("values");
321 }
322 // TODO: Check for ICollection and get the Count, to optimize?
323 foreach (T item in values)
324 {
325 Add(item);
326 }
327 }
328
329 /// <summary>
330 /// Returns an enumerator that iterates through the collection.
331 /// </summary>
332 /// <returns>
333 /// An enumerator that can be used to iterate through the collection.
334 /// </returns>
335 public IEnumerator<T> GetEnumerator()
336 {
337 for (int i = 0; i < count; i++)
338 {
339 yield return array[i];
340 }
341 }
342
343 /// <summary>
344 /// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
345 /// </summary>
346 /// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
347 /// <returns>
348 /// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
349 /// </returns>
350 public override bool Equals(object obj)
351 {
352 return Equals(obj as RepeatedField<T>);
353 }
354
355 /// <summary>
356 /// Returns an enumerator that iterates through a collection.
357 /// </summary>
358 /// <returns>
359 /// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
360 /// </returns>
361 IEnumerator IEnumerable.GetEnumerator()
362 {
363 return GetEnumerator();
364 }
365
366 /// <summary>
367 /// Returns a hash code for this instance.
368 /// </summary>
369 /// <returns>
370 /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
371 /// </returns>
372 public override int GetHashCode()
373 {
374 int hash = 0;
375 for (int i = 0; i < count; i++)
376 {
377 hash = hash * 31 + array[i].GetHashCode();
378 }
379 return hash;
380 }
381
382 /// <summary>
383 /// Compares this repeated field with another for equality.
384 /// </summary>
385 /// <param name="other">The repeated field to compare this with.</param>
386 /// <returns><c>true</c> if <paramref name="other"/> refers to an equal repeated field; <c>false</c> otherwise.</returns>
387 public bool Equals(RepeatedField<T> other)
388 {
389 if (ReferenceEquals(other, null))
390 {
391 return false;
392 }
393 if (ReferenceEquals(other, this))
394 {
395 return true;
396 }
397 if (other.Count != this.Count)
398 {
399 return false;
400 }
401 EqualityComparer<T> comparer = EqualityComparer<T>.Default;
402 for (int i = 0; i < count; i++)
403 {
404 if (!comparer.Equals(array[i], other.array[i]))
405 {
406 return false;
407 }
408 }
409 return true;
410 }
411
412 /// <summary>
413 /// Returns the index of the given item within the collection, or -1 if the item is not
414 /// present.
415 /// </summary>
416 /// <param name="item">The item to find in the collection.</param>
417 /// <returns>The zero-based index of the item, or -1 if it is not found.</returns>
418 public int IndexOf(T item)
419 {
420 if (item == null)
421 {
422 throw new ArgumentNullException("item");
423 }
424 EqualityComparer<T> comparer = EqualityComparer<T>.Default;
425 for (int i = 0; i < count; i++)
426 {
427 if (comparer.Equals(array[i], item))
428 {
429 return i;
430 }
431 }
432 return -1;
433 }
434
435 /// <summary>
436 /// Inserts the given item at the specified index.
437 /// </summary>
438 /// <param name="index">The index at which to insert the item.</param>
439 /// <param name="item">The item to insert.</param>
440 public void Insert(int index, T item)
441 {
442 if (item == null)
443 {
444 throw new ArgumentNullException("item");
445 }
446 if (index < 0 || index > count)
447 {
448 throw new ArgumentOutOfRangeException("index");
449 }
450 EnsureSize(count + 1);
451 Array.Copy(array, index, array, index + 1, count - index);
452 array[index] = item;
453 count++;
454 }
455
456 /// <summary>
457 /// Removes the item at the given index.
458 /// </summary>
459 /// <param name="index">The zero-based index of the item to remove.</param>
460 public void RemoveAt(int index)
461 {
462 if (index < 0 || index >= count)
463 {
464 throw new ArgumentOutOfRangeException("index");
465 }
466 Array.Copy(array, index + 1, array, index, count - index - 1);
467 count--;
468 array[count] = default(T);
469 }
470
471 /// <summary>
472 /// Returns a string representation of this repeated field, in the same
473 /// way as it would be represented by the default JSON formatter.
474 /// </summary>
475 public override string ToString()
476 {
477 var builder = new StringBuilder();
478 JsonFormatter.Default.WriteList(builder, this);
479 return builder.ToString();
480 }
481
482 /// <summary>
483 /// Gets or sets the item at the specified index.
484 /// </summary>
485 /// <value>
486 /// The element at the specified index.
487 /// </value>
488 /// <param name="index">The zero-based index of the element to get or set.</param>
489 /// <returns>The item at the specified index.</returns>
490 public T this[int index]
491 {
492 get
493 {
494 if (index < 0 || index >= count)
495 {
496 throw new ArgumentOutOfRangeException("index");
497 }
498 return array[index];
499 }
500 set
501 {
502 if (index < 0 || index >= count)
503 {
504 throw new ArgumentOutOfRangeException("index");
505 }
506 if (value == null)
507 {
508 throw new ArgumentNullException("value");
509 }
510 array[index] = value;
511 }
512 }
513
514 #region Explicit interface implementation for IList and ICollection.
515 bool IList.IsFixedSize { get { return false; } }
516
517 void ICollection.CopyTo(Array array, int index)
518 {
519 Array.Copy(this.array, 0, array, index, count);
520 }
521
522 bool ICollection.IsSynchronized { get { return false; } }
523
524 object ICollection.SyncRoot { get { return this; } }
525
526 object IList.this[int index]
527 {
528 get { return this[index]; }
529 set { this[index] = (T)value; }
530 }
531
532 int IList.Add(object value)
533 {
534 Add((T) value);
535 return count - 1;
536 }
537
538 bool IList.Contains(object value)
539 {
540 return (value is T && Contains((T)value));
541 }
542
543 int IList.IndexOf(object value)
544 {
545 if (!(value is T))
546 {
547 return -1;
548 }
549 return IndexOf((T)value);
550 }
551
552 void IList.Insert(int index, object value)
553 {
554 Insert(index, (T) value);
555 }
556
557 void IList.Remove(object value)
558 {
559 if (!(value is T))
560 {
561 return;
562 }
563 Remove((T)value);
564 }
565 #endregion
566 }
567}