• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #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 
33 using System;
34 using System.Collections;
35 using System.Collections.Generic;
36 using System.IO;
37 using System.Security;
38 using System.Threading;
39 
40 namespace Google.Protobuf.Collections
41 {
42     /// <summary>
43     /// The contents of a repeated field: essentially, a collection with some extra
44     /// restrictions (no null values) and capabilities (deep cloning).
45     /// </summary>
46     /// <remarks>
47     /// This implementation does not generally prohibit the use of types which are not
48     /// supported by Protocol Buffers but nor does it guarantee that all operations will work in such cases.
49     /// </remarks>
50     /// <typeparam name="T">The element type of the repeated field.</typeparam>
51     public sealed class RepeatedField<T> : IList<T>, IList, IDeepCloneable<RepeatedField<T>>, IEquatable<RepeatedField<T>>
52 #if !NET35
53         , IReadOnlyList<T>
54 #endif
55     {
56         private static readonly EqualityComparer<T> EqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<T>();
57         private static readonly T[] EmptyArray = new T[0];
58         private const int MinArraySize = 8;
59 
60         private T[] array = EmptyArray;
61         private int count = 0;
62 
63         /// <summary>
64         /// Creates a deep clone of this repeated field.
65         /// </summary>
66         /// <remarks>
67         /// If the field type is
68         /// a message type, each element is also cloned; otherwise, it is
69         /// assumed that the field type is primitive (including string and
70         /// bytes, both of which are immutable) and so a simple copy is
71         /// equivalent to a deep clone.
72         /// </remarks>
73         /// <returns>A deep clone of this repeated field.</returns>
Clone()74         public RepeatedField<T> Clone()
75         {
76             RepeatedField<T> clone = new RepeatedField<T>();
77             if (array != EmptyArray)
78             {
79                 clone.array = (T[])array.Clone();
80                 IDeepCloneable<T>[] cloneableArray = clone.array as IDeepCloneable<T>[];
81                 if (cloneableArray != null)
82                 {
83                     for (int i = 0; i < count; i++)
84                     {
85                         clone.array[i] = cloneableArray[i].Clone();
86                     }
87                 }
88             }
89             clone.count = count;
90             return clone;
91         }
92 
93         /// <summary>
94         /// Adds the entries from the given input stream, decoding them with the specified codec.
95         /// </summary>
96         /// <param name="input">The input stream to read from.</param>
97         /// <param name="codec">The codec to use in order to read each entry.</param>
AddEntriesFrom(CodedInputStream input, FieldCodec<T> codec)98         public void AddEntriesFrom(CodedInputStream input, FieldCodec<T> codec)
99         {
100             ParseContext.Initialize(input, out ParseContext ctx);
101             try
102             {
103                 AddEntriesFrom(ref ctx, codec);
104             }
105             finally
106             {
107                 ctx.CopyStateTo(input);
108             }
109         }
110 
111         /// <summary>
112         /// Adds the entries from the given parse context, decoding them with the specified codec.
113         /// </summary>
114         /// <param name="ctx">The input to read from.</param>
115         /// <param name="codec">The codec to use in order to read each entry.</param>
116         [SecuritySafeCritical]
AddEntriesFrom(ref ParseContext ctx, FieldCodec<T> codec)117         public void AddEntriesFrom(ref ParseContext ctx, FieldCodec<T> codec)
118         {
119             // TODO: Inline some of the Add code, so we can avoid checking the size on every
120             // iteration.
121             uint tag = ctx.state.lastTag;
122             var reader = codec.ValueReader;
123             // Non-nullable value types can be packed or not.
124             if (FieldCodec<T>.IsPackedRepeatedField(tag))
125             {
126                 int length = ctx.ReadLength();
127                 if (length > 0)
128                 {
129                     int oldLimit = SegmentedBufferHelper.PushLimit(ref ctx.state, length);
130 
131                     // If the content is fixed size then we can calculate the length
132                     // of the repeated field and pre-initialize the underlying collection.
133                     //
134                     // Check that the supplied length doesn't exceed the underlying buffer.
135                     // That prevents a malicious length from initializing a very large collection.
136                     if (codec.FixedSize > 0 && length % codec.FixedSize == 0 && ParsingPrimitives.IsDataAvailable(ref ctx.state, length))
137                     {
138                         EnsureSize(count + (length / codec.FixedSize));
139 
140                         while (!SegmentedBufferHelper.IsReachedLimit(ref ctx.state))
141                         {
142                             // Only FieldCodecs with a fixed size can reach here, and they are all known
143                             // types that don't allow the user to specify a custom reader action.
144                             // reader action will never return null.
145                             array[count++] = reader(ref ctx);
146                         }
147                     }
148                     else
149                     {
150                         // Content is variable size so add until we reach the limit.
151                         while (!SegmentedBufferHelper.IsReachedLimit(ref ctx.state))
152                         {
153                             Add(reader(ref ctx));
154                         }
155                     }
156                     SegmentedBufferHelper.PopLimit(ref ctx.state, oldLimit);
157                 }
158                 // Empty packed field. Odd, but valid - just ignore.
159             }
160             else
161             {
162                 // Not packed... (possibly not packable)
163                 do
164                 {
165                     Add(reader(ref ctx));
166                 } while (ParsingPrimitives.MaybeConsumeTag(ref ctx.buffer, ref ctx.state, tag));
167             }
168         }
169 
170         /// <summary>
171         /// Calculates the size of this collection based on the given codec.
172         /// </summary>
173         /// <param name="codec">The codec to use when encoding each field.</param>
174         /// <returns>The number of bytes that would be written to an output by one of the <c>WriteTo</c> methods,
175         /// using the same codec.</returns>
CalculateSize(FieldCodec<T> codec)176         public int CalculateSize(FieldCodec<T> codec)
177         {
178             if (count == 0)
179             {
180                 return 0;
181             }
182             uint tag = codec.Tag;
183             if (codec.PackedRepeatedField)
184             {
185                 int dataSize = CalculatePackedDataSize(codec);
186                 return CodedOutputStream.ComputeRawVarint32Size(tag) +
187                     CodedOutputStream.ComputeLengthSize(dataSize) +
188                     dataSize;
189             }
190             else
191             {
192                 var sizeCalculator = codec.ValueSizeCalculator;
193                 int size = count * CodedOutputStream.ComputeRawVarint32Size(tag);
194                 if (codec.EndTag != 0)
195                 {
196                     size += count * CodedOutputStream.ComputeRawVarint32Size(codec.EndTag);
197                 }
198                 for (int i = 0; i < count; i++)
199                 {
200                     size += sizeCalculator(array[i]);
201                 }
202                 return size;
203             }
204         }
205 
CalculatePackedDataSize(FieldCodec<T> codec)206         private int CalculatePackedDataSize(FieldCodec<T> codec)
207         {
208             int fixedSize = codec.FixedSize;
209             if (fixedSize == 0)
210             {
211                 var calculator = codec.ValueSizeCalculator;
212                 int tmp = 0;
213                 for (int i = 0; i < count; i++)
214                 {
215                     tmp += calculator(array[i]);
216                 }
217                 return tmp;
218             }
219             else
220             {
221                 return fixedSize * Count;
222             }
223         }
224 
225         /// <summary>
226         /// Writes the contents of this collection to the given <see cref="CodedOutputStream"/>,
227         /// encoding each value using the specified codec.
228         /// </summary>
229         /// <param name="output">The output stream to write to.</param>
230         /// <param name="codec">The codec to use when encoding each value.</param>
WriteTo(CodedOutputStream output, FieldCodec<T> codec)231         public void WriteTo(CodedOutputStream output, FieldCodec<T> codec)
232         {
233             WriteContext.Initialize(output, out WriteContext ctx);
234             try
235             {
236                 WriteTo(ref ctx, codec);
237             }
238             finally
239             {
240                 ctx.CopyStateTo(output);
241             }
242         }
243 
244         /// <summary>
245         /// Writes the contents of this collection to the given write context,
246         /// encoding each value using the specified codec.
247         /// </summary>
248         /// <param name="ctx">The write context to write to.</param>
249         /// <param name="codec">The codec to use when encoding each value.</param>
250         [SecuritySafeCritical]
WriteTo(ref WriteContext ctx, FieldCodec<T> codec)251         public void WriteTo(ref WriteContext ctx, FieldCodec<T> codec)
252         {
253             if (count == 0)
254             {
255                 return;
256             }
257             var writer = codec.ValueWriter;
258             var tag = codec.Tag;
259             if (codec.PackedRepeatedField)
260             {
261                 // Packed primitive type
262                 int size = CalculatePackedDataSize(codec);
263                 ctx.WriteTag(tag);
264                 ctx.WriteLength(size);
265                 for (int i = 0; i < count; i++)
266                 {
267                     writer(ref ctx, array[i]);
268                 }
269             }
270             else
271             {
272                 // Not packed: a simple tag/value pair for each value.
273                 // Can't use codec.WriteTagAndValue, as that omits default values.
274                 for (int i = 0; i < count; i++)
275                 {
276                     ctx.WriteTag(tag);
277                     writer(ref ctx, array[i]);
278                     if (codec.EndTag != 0)
279                     {
280                         ctx.WriteTag(codec.EndTag);
281                     }
282                 }
283             }
284         }
285 
286         /// <summary>
287         /// Gets and sets the capacity of the RepeatedField's internal array.  WHen set, the internal array is reallocated to the given capacity.
288         /// <exception cref="ArgumentOutOfRangeException">The new value is less than Count -or- when Count is less than 0.</exception>
289         /// </summary>
290         public int Capacity
291         {
292             get { return array.Length; }
293             set
294             {
295                 if (value < count)
296                 {
297                     throw new ArgumentOutOfRangeException("Capacity", value,
298                         $"Cannot set Capacity to a value smaller than the current item count, {count}");
299                 }
300 
301                 if (value >= 0 && value != array.Length)
302                 {
303                     SetSize(value);
304                 }
305             }
306         }
307 
308         // May increase the size of the internal array, but will never shrink it.
EnsureSize(int size)309         private void EnsureSize(int size)
310         {
311             if (array.Length < size)
312             {
313                 size = Math.Max(size, MinArraySize);
314                 int newSize = Math.Max(array.Length * 2, size);
315                 SetSize(newSize);
316             }
317         }
318 
319         // Sets the internal array to an exact size.
SetSize(int size)320         private void SetSize(int size)
321         {
322             if (size != array.Length)
323             {
324                 var tmp = new T[size];
325                 Array.Copy(array, 0, tmp, 0, count);
326                 array = tmp;
327             }
328         }
329 
330         /// <summary>
331         /// Adds the specified item to the collection.
332         /// </summary>
333         /// <param name="item">The item to add.</param>
Add(T item)334         public void Add(T item)
335         {
336             ProtoPreconditions.CheckNotNullUnconstrained(item, nameof(item));
337             EnsureSize(count + 1);
338             array[count++] = item;
339         }
340 
341         /// <summary>
342         /// Removes all items from the collection.
343         /// </summary>
Clear()344         public void Clear()
345         {
346             array = EmptyArray;
347             count = 0;
348         }
349 
350         /// <summary>
351         /// Determines whether this collection contains the given item.
352         /// </summary>
353         /// <param name="item">The item to find.</param>
354         /// <returns><c>true</c> if this collection contains the given item; <c>false</c> otherwise.</returns>
Contains(T item)355         public bool Contains(T item)
356         {
357             return IndexOf(item) != -1;
358         }
359 
360         /// <summary>
361         /// Copies this collection to the given array.
362         /// </summary>
363         /// <param name="array">The array to copy to.</param>
364         /// <param name="arrayIndex">The first index of the array to copy to.</param>
CopyTo(T[] array, int arrayIndex)365         public void CopyTo(T[] array, int arrayIndex)
366         {
367             Array.Copy(this.array, 0, array, arrayIndex, count);
368         }
369 
370         /// <summary>
371         /// Removes the specified item from the collection
372         /// </summary>
373         /// <param name="item">The item to remove.</param>
374         /// <returns><c>true</c> if the item was found and removed; <c>false</c> otherwise.</returns>
Remove(T item)375         public bool Remove(T item)
376         {
377             int index = IndexOf(item);
378             if (index == -1)
379             {
380                 return false;
381             }
382             Array.Copy(array, index + 1, array, index, count - index - 1);
383             count--;
384             array[count] = default(T);
385             return true;
386         }
387 
388         /// <summary>
389         /// Gets the number of elements contained in the collection.
390         /// </summary>
391         public int Count => count;
392 
393         /// <summary>
394         /// Gets a value indicating whether the collection is read-only.
395         /// </summary>
396         public bool IsReadOnly => false;
397 
398         /// <summary>
399         /// Adds all of the specified values into this collection.
400         /// </summary>
401         /// <param name="values">The values to add to this collection.</param>
AddRange(IEnumerable<T> values)402         public void AddRange(IEnumerable<T> values)
403         {
404             ProtoPreconditions.CheckNotNull(values, nameof(values));
405 
406             // Optimization 1: If the collection we're adding is already a RepeatedField<T>,
407             // we know the values are valid.
408             var otherRepeatedField = values as RepeatedField<T>;
409             if (otherRepeatedField != null)
410             {
411                 EnsureSize(count + otherRepeatedField.count);
412                 Array.Copy(otherRepeatedField.array, 0, array, count, otherRepeatedField.count);
413                 count += otherRepeatedField.count;
414                 return;
415             }
416 
417             // Optimization 2: The collection is an ICollection, so we can expand
418             // just once and ask the collection to copy itself into the array.
419             var collection = values as ICollection;
420             if (collection != null)
421             {
422                 var extraCount = collection.Count;
423                 // For reference types and nullable value types, we need to check that there are no nulls
424                 // present. (This isn't a thread-safe approach, but we don't advertise this is thread-safe.)
425                 // We expect the JITter to optimize this test to true/false, so it's effectively conditional
426                 // specialization.
427                 if (default(T) == null)
428                 {
429                     // TODO: Measure whether iterating once to check and then letting the collection copy
430                     // itself is faster or slower than iterating and adding as we go. For large
431                     // collections this will not be great in terms of cache usage... but the optimized
432                     // copy may be significantly faster than doing it one at a time.
433                     foreach (var item in collection)
434                     {
435                         if (item == null)
436                         {
437                             throw new ArgumentException("Sequence contained null element", nameof(values));
438                         }
439                     }
440                 }
441                 EnsureSize(count + extraCount);
442                 collection.CopyTo(array, count);
443                 count += extraCount;
444                 return;
445             }
446 
447             // We *could* check for ICollection<T> as well, but very very few collections implement
448             // ICollection<T> but not ICollection. (HashSet<T> does, for one...)
449 
450             // Fall back to a slower path of adding items one at a time.
451             foreach (T item in values)
452             {
453                 Add(item);
454             }
455         }
456 
457         /// <summary>
458         /// Adds all of the specified values into this collection. This method is present to
459         /// allow repeated fields to be constructed from queries within collection initializers.
460         /// Within non-collection-initializer code, consider using the equivalent <see cref="AddRange"/>
461         /// method instead for clarity.
462         /// </summary>
463         /// <param name="values">The values to add to this collection.</param>
Add(IEnumerable<T> values)464         public void Add(IEnumerable<T> values)
465         {
466             AddRange(values);
467         }
468 
469         /// <summary>
470         /// Returns an enumerator that iterates through the collection.
471         /// </summary>
472         /// <returns>
473         /// An enumerator that can be used to iterate through the collection.
474         /// </returns>
GetEnumerator()475         public IEnumerator<T> GetEnumerator()
476         {
477             for (int i = 0; i < count; i++)
478             {
479                 yield return array[i];
480             }
481         }
482 
483         /// <summary>
484         /// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
485         /// </summary>
486         /// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
487         /// <returns>
488         ///   <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
489         /// </returns>
Equals(object obj)490         public override bool Equals(object obj)
491         {
492             return Equals(obj as RepeatedField<T>);
493         }
494 
495         /// <summary>
496         /// Returns an enumerator that iterates through a collection.
497         /// </summary>
498         /// <returns>
499         /// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
500         /// </returns>
IEnumerable.GetEnumerator()501         IEnumerator IEnumerable.GetEnumerator()
502         {
503             return GetEnumerator();
504         }
505 
506         /// <summary>
507         /// Returns a hash code for this instance.
508         /// </summary>
509         /// <returns>
510         /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
511         /// </returns>
GetHashCode()512         public override int GetHashCode()
513         {
514             int hash = 0;
515             for (int i = 0; i < count; i++)
516             {
517                 hash = hash * 31 + array[i].GetHashCode();
518             }
519             return hash;
520         }
521 
522         /// <summary>
523         /// Compares this repeated field with another for equality.
524         /// </summary>
525         /// <param name="other">The repeated field to compare this with.</param>
526         /// <returns><c>true</c> if <paramref name="other"/> refers to an equal repeated field; <c>false</c> otherwise.</returns>
Equals(RepeatedField<T> other)527         public bool Equals(RepeatedField<T> other)
528         {
529             if (ReferenceEquals(other, null))
530             {
531                 return false;
532             }
533             if (ReferenceEquals(other, this))
534             {
535                 return true;
536             }
537             if (other.Count != this.Count)
538             {
539                 return false;
540             }
541             EqualityComparer<T> comparer = EqualityComparer;
542             for (int i = 0; i < count; i++)
543             {
544                 if (!comparer.Equals(array[i], other.array[i]))
545                 {
546                     return false;
547                 }
548             }
549             return true;
550         }
551 
552         /// <summary>
553         /// Returns the index of the given item within the collection, or -1 if the item is not
554         /// present.
555         /// </summary>
556         /// <param name="item">The item to find in the collection.</param>
557         /// <returns>The zero-based index of the item, or -1 if it is not found.</returns>
IndexOf(T item)558         public int IndexOf(T item)
559         {
560             ProtoPreconditions.CheckNotNullUnconstrained(item, nameof(item));
561             EqualityComparer<T> comparer = EqualityComparer;
562             for (int i = 0; i < count; i++)
563             {
564                 if (comparer.Equals(array[i], item))
565                 {
566                     return i;
567                 }
568             }
569             return -1;
570         }
571 
572         /// <summary>
573         /// Inserts the given item at the specified index.
574         /// </summary>
575         /// <param name="index">The index at which to insert the item.</param>
576         /// <param name="item">The item to insert.</param>
Insert(int index, T item)577         public void Insert(int index, T item)
578         {
579             ProtoPreconditions.CheckNotNullUnconstrained(item, nameof(item));
580             if (index < 0 || index > count)
581             {
582                 throw new ArgumentOutOfRangeException(nameof(index));
583             }
584             EnsureSize(count + 1);
585             Array.Copy(array, index, array, index + 1, count - index);
586             array[index] = item;
587             count++;
588         }
589 
590         /// <summary>
591         /// Removes the item at the given index.
592         /// </summary>
593         /// <param name="index">The zero-based index of the item to remove.</param>
RemoveAt(int index)594         public void RemoveAt(int index)
595         {
596             if (index < 0 || index >= count)
597             {
598                 throw new ArgumentOutOfRangeException(nameof(index));
599             }
600             Array.Copy(array, index + 1, array, index, count - index - 1);
601             count--;
602             array[count] = default(T);
603         }
604 
605         /// <summary>
606         /// Returns a string representation of this repeated field, in the same
607         /// way as it would be represented by the default JSON formatter.
608         /// </summary>
ToString()609         public override string ToString()
610         {
611             var writer = new StringWriter();
612             JsonFormatter.Default.WriteList(writer, this);
613             return writer.ToString();
614         }
615 
616         /// <summary>
617         /// Gets or sets the item at the specified index.
618         /// </summary>
619         /// <value>
620         /// The element at the specified index.
621         /// </value>
622         /// <param name="index">The zero-based index of the element to get or set.</param>
623         /// <returns>The item at the specified index.</returns>
624         public T this[int index]
625         {
626             get
627             {
628                 if (index < 0 || index >= count)
629                 {
630                     throw new ArgumentOutOfRangeException(nameof(index));
631                 }
632                 return array[index];
633             }
634             set
635             {
636                 if (index < 0 || index >= count)
637                 {
638                     throw new ArgumentOutOfRangeException(nameof(index));
639                 }
640                 ProtoPreconditions.CheckNotNullUnconstrained(value, nameof(value));
641                 array[index] = value;
642             }
643         }
644 
645         #region Explicit interface implementation for IList and ICollection.
646         bool IList.IsFixedSize => false;
647 
ICollection.CopyTo(Array array, int index)648         void ICollection.CopyTo(Array array, int index)
649         {
650             Array.Copy(this.array, 0, array, index, count);
651         }
652 
653         bool ICollection.IsSynchronized => false;
654 
655         object ICollection.SyncRoot => this;
656 
657         object IList.this[int index]
658         {
659             get { return this[index]; }
660             set { this[index] = (T)value; }
661         }
662 
IList.Add(object value)663         int IList.Add(object value)
664         {
665             Add((T) value);
666             return count - 1;
667         }
668 
IList.Contains(object value)669         bool IList.Contains(object value)
670         {
671             return (value is T && Contains((T)value));
672         }
673 
IList.IndexOf(object value)674         int IList.IndexOf(object value)
675         {
676             if (!(value is T))
677             {
678                 return -1;
679             }
680             return IndexOf((T)value);
681         }
682 
IList.Insert(int index, object value)683         void IList.Insert(int index, object value)
684         {
685             Insert(index, (T) value);
686         }
687 
IList.Remove(object value)688         void IList.Remove(object value)
689         {
690             if (!(value is T))
691             {
692                 return;
693             }
694             Remove((T)value);
695         }
696         #endregion
697     }
698 }
699