OLD | NEW |
| (Empty) |
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 Google.Protobuf.Reflection; | |
34 using System; | |
35 using System.Collections; | |
36 using System.Collections.Generic; | |
37 using System.Linq; | |
38 using Google.Protobuf.Compatibility; | |
39 | |
40 namespace Google.Protobuf.Collections | |
41 { | |
42 /// <summary> | |
43 /// Representation of a map field in a Protocol Buffer message. | |
44 /// </summary> | |
45 /// <typeparam name="TKey">Key type in the map. Must be a type supported by
Protocol Buffer map keys.</typeparam> | |
46 /// <typeparam name="TValue">Value type in the map. Must be a type supported
by Protocol Buffers.</typeparam> | |
47 /// <remarks> | |
48 /// This implementation preserves insertion order for simplicity of testing | |
49 /// code using maps fields. Overwriting an existing entry does not change th
e | |
50 /// position of that entry within the map. Equality is not order-sensitive. | |
51 /// For string keys, the equality comparison is provided by <see cref="Strin
gComparer.Ordinal" />. | |
52 /// </remarks> | |
53 public sealed class MapField<TKey, TValue> : IDeepCloneable<MapField<TKey, T
Value>>, IDictionary<TKey, TValue>, IEquatable<MapField<TKey, TValue>>, IDiction
ary | |
54 { | |
55 // TODO: Don't create the map/list until we have an entry. (Assume many
maps will be empty.) | |
56 private readonly bool allowNullValues; | |
57 private readonly Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TVal
ue>>> map = | |
58 new Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>(); | |
59 private readonly LinkedList<KeyValuePair<TKey, TValue>> list = new Linke
dList<KeyValuePair<TKey, TValue>>(); | |
60 | |
61 /// <summary> | |
62 /// Constructs a new map field, defaulting the value nullability to only
allow null values for message types | |
63 /// and non-nullable value types. | |
64 /// </summary> | |
65 public MapField() : this(typeof(IMessage).IsAssignableFrom(typeof(TValue
)) || Nullable.GetUnderlyingType(typeof(TValue)) != null) | |
66 { | |
67 } | |
68 | |
69 /// <summary> | |
70 /// Constructs a new map field, overriding the choice of whether null va
lues are permitted in the map. | |
71 /// This is used by wrapper types, where maps with string and bytes wrap
pers as the value types | |
72 /// support null values. | |
73 /// </summary> | |
74 /// <param name="allowNullValues">Whether null values are permitted in t
he map or not.</param> | |
75 public MapField(bool allowNullValues) | |
76 { | |
77 if (allowNullValues && typeof(TValue).IsValueType() && Nullable.GetU
nderlyingType(typeof(TValue)) == null) | |
78 { | |
79 throw new ArgumentException("allowNullValues", "Non-nullable val
ue types do not support null values"); | |
80 } | |
81 this.allowNullValues = allowNullValues; | |
82 } | |
83 | |
84 /// <summary> | |
85 /// Creates a deep clone of this object. | |
86 /// </summary> | |
87 /// <returns> | |
88 /// A deep clone of this object. | |
89 /// </returns> | |
90 public MapField<TKey, TValue> Clone() | |
91 { | |
92 var clone = new MapField<TKey, TValue>(allowNullValues); | |
93 // Keys are never cloneable. Values might be. | |
94 if (typeof(IDeepCloneable<TValue>).IsAssignableFrom(typeof(TValue))) | |
95 { | |
96 foreach (var pair in list) | |
97 { | |
98 clone.Add(pair.Key, pair.Value == null ? pair.Value : ((IDee
pCloneable<TValue>)pair.Value).Clone()); | |
99 } | |
100 } | |
101 else | |
102 { | |
103 // Nothing is cloneable, so we don't need to worry. | |
104 clone.Add(this); | |
105 } | |
106 return clone; | |
107 } | |
108 | |
109 /// <summary> | |
110 /// Adds the specified key/value pair to the map. | |
111 /// </summary> | |
112 /// <remarks> | |
113 /// This operation fails if the key already exists in the map. To replac
e an existing entry, use the indexer. | |
114 /// </remarks> | |
115 /// <param name="key">The key to add</param> | |
116 /// <param name="value">The value to add.</param> | |
117 /// <exception cref="System.ArgumentException">The given key already exi
sts in map.</exception> | |
118 public void Add(TKey key, TValue value) | |
119 { | |
120 // Validation of arguments happens in ContainsKey and the indexer | |
121 if (ContainsKey(key)) | |
122 { | |
123 throw new ArgumentException("Key already exists in map", "key"); | |
124 } | |
125 this[key] = value; | |
126 } | |
127 | |
128 /// <summary> | |
129 /// Determines whether the specified key is present in the map. | |
130 /// </summary> | |
131 /// <param name="key">The key to check.</param> | |
132 /// <returns><c>true</c> if the map contains the given key; <c>false</c>
otherwise.</returns> | |
133 public bool ContainsKey(TKey key) | |
134 { | |
135 Preconditions.CheckNotNullUnconstrained(key, "key"); | |
136 return map.ContainsKey(key); | |
137 } | |
138 | |
139 private bool ContainsValue(TValue value) | |
140 { | |
141 var comparer = EqualityComparer<TValue>.Default; | |
142 return list.Any(pair => comparer.Equals(pair.Value, value)); | |
143 } | |
144 | |
145 /// <summary> | |
146 /// Removes the entry identified by the given key from the map. | |
147 /// </summary> | |
148 /// <param name="key">The key indicating the entry to remove from the ma
p.</param> | |
149 /// <returns><c>true</c> if the map contained the given key before the e
ntry was removed; <c>false</c> otherwise.</returns> | |
150 public bool Remove(TKey key) | |
151 { | |
152 Preconditions.CheckNotNullUnconstrained(key, "key"); | |
153 LinkedListNode<KeyValuePair<TKey, TValue>> node; | |
154 if (map.TryGetValue(key, out node)) | |
155 { | |
156 map.Remove(key); | |
157 node.List.Remove(node); | |
158 return true; | |
159 } | |
160 else | |
161 { | |
162 return false; | |
163 } | |
164 } | |
165 | |
166 /// <summary> | |
167 /// Gets the value associated with the specified key. | |
168 /// </summary> | |
169 /// <param name="key">The key whose value to get.</param> | |
170 /// <param name="value">When this method returns, the value associated w
ith the specified key, if the key is found; | |
171 /// otherwise, the default value for the type of the <paramref name="val
ue"/> parameter. | |
172 /// This parameter is passed uninitialized.</param> | |
173 /// <returns><c>true</c> if the map contains an element with the specifi
ed key; otherwise, <c>false</c>.</returns> | |
174 public bool TryGetValue(TKey key, out TValue value) | |
175 { | |
176 LinkedListNode<KeyValuePair<TKey, TValue>> node; | |
177 if (map.TryGetValue(key, out node)) | |
178 { | |
179 value = node.Value.Value; | |
180 return true; | |
181 } | |
182 else | |
183 { | |
184 value = default(TValue); | |
185 return false; | |
186 } | |
187 } | |
188 | |
189 /// <summary> | |
190 /// Gets or sets the value associated with the specified key. | |
191 /// </summary> | |
192 /// <param name="key">The key of the value to get or set.</param> | |
193 /// <exception cref="KeyNotFoundException">The property is retrieved and
key does not exist in the collection.</exception> | |
194 /// <returns>The value associated with the specified key. If the specifi
ed key is not found, | |
195 /// a get operation throws a <see cref="KeyNotFoundException"/>, and a s
et operation creates a new element with the specified key.</returns> | |
196 public TValue this[TKey key] | |
197 { | |
198 get | |
199 { | |
200 Preconditions.CheckNotNullUnconstrained(key, "key"); | |
201 TValue value; | |
202 if (TryGetValue(key, out value)) | |
203 { | |
204 return value; | |
205 } | |
206 throw new KeyNotFoundException(); | |
207 } | |
208 set | |
209 { | |
210 Preconditions.CheckNotNullUnconstrained(key, "key"); | |
211 // value == null check here is redundant, but avoids boxing. | |
212 if (value == null && !allowNullValues) | |
213 { | |
214 Preconditions.CheckNotNullUnconstrained(value, "value"); | |
215 } | |
216 LinkedListNode<KeyValuePair<TKey, TValue>> node; | |
217 var pair = new KeyValuePair<TKey, TValue>(key, value); | |
218 if (map.TryGetValue(key, out node)) | |
219 { | |
220 node.Value = pair; | |
221 } | |
222 else | |
223 { | |
224 node = list.AddLast(pair); | |
225 map[key] = node; | |
226 } | |
227 } | |
228 } | |
229 | |
230 /// <summary> | |
231 /// Gets a collection containing the keys in the map. | |
232 /// </summary> | |
233 public ICollection<TKey> Keys { get { return new MapView<TKey>(this, pai
r => pair.Key, ContainsKey); } } | |
234 | |
235 /// <summary> | |
236 /// Gets a collection containing the values in the map. | |
237 /// </summary> | |
238 public ICollection<TValue> Values { get { return new MapView<TValue>(thi
s, pair => pair.Value, ContainsValue); } } | |
239 | |
240 /// <summary> | |
241 /// Adds the specified entries to the map. | |
242 /// </summary> | |
243 /// <param name="entries">The entries to add to the map.</param> | |
244 public void Add(IDictionary<TKey, TValue> entries) | |
245 { | |
246 Preconditions.CheckNotNull(entries, "entries"); | |
247 foreach (var pair in entries) | |
248 { | |
249 Add(pair.Key, pair.Value); | |
250 } | |
251 } | |
252 | |
253 /// <summary> | |
254 /// Returns an enumerator that iterates through the collection. | |
255 /// </summary> | |
256 /// <returns> | |
257 /// An enumerator that can be used to iterate through the collection. | |
258 /// </returns> | |
259 public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() | |
260 { | |
261 return list.GetEnumerator(); | |
262 } | |
263 | |
264 /// <summary> | |
265 /// Returns an enumerator that iterates through a collection. | |
266 /// </summary> | |
267 /// <returns> | |
268 /// An <see cref="T:System.Collections.IEnumerator" /> object that can b
e used to iterate through the collection. | |
269 /// </returns> | |
270 IEnumerator IEnumerable.GetEnumerator() | |
271 { | |
272 return GetEnumerator(); | |
273 } | |
274 | |
275 /// <summary> | |
276 /// Adds the specified item to the map. | |
277 /// </summary> | |
278 /// <param name="item">The item to add to the map.</param> | |
279 void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TVal
ue> item) | |
280 { | |
281 Add(item.Key, item.Value); | |
282 } | |
283 | |
284 /// <summary> | |
285 /// Removes all items from the map. | |
286 /// </summary> | |
287 public void Clear() | |
288 { | |
289 list.Clear(); | |
290 map.Clear(); | |
291 } | |
292 | |
293 /// <summary> | |
294 /// Determines whether map contains an entry equivalent to the given key
/value pair. | |
295 /// </summary> | |
296 /// <param name="item">The key/value pair to find.</param> | |
297 /// <returns></returns> | |
298 bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey,
TValue> item) | |
299 { | |
300 TValue value; | |
301 return TryGetValue(item.Key, out value) | |
302 && EqualityComparer<TValue>.Default.Equals(item.Value, value); | |
303 } | |
304 | |
305 /// <summary> | |
306 /// Copies the key/value pairs in this map to an array. | |
307 /// </summary> | |
308 /// <param name="array">The array to copy the entries into.</param> | |
309 /// <param name="arrayIndex">The index of the array at which to start co
pying values.</param> | |
310 void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, T
Value>[] array, int arrayIndex) | |
311 { | |
312 list.CopyTo(array, arrayIndex); | |
313 } | |
314 | |
315 /// <summary> | |
316 /// Removes the specified key/value pair from the map. | |
317 /// </summary> | |
318 /// <remarks>Both the key and the value must be found for the entry to b
e removed.</remarks> | |
319 /// <param name="item">The key/value pair to remove.</param> | |
320 /// <returns><c>true</c> if the key/value pair was found and removed; <c
>false</c> otherwise.</returns> | |
321 bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, T
Value> item) | |
322 { | |
323 if (item.Key == null) | |
324 { | |
325 throw new ArgumentException("Key is null", "item"); | |
326 } | |
327 LinkedListNode<KeyValuePair<TKey, TValue>> node; | |
328 if (map.TryGetValue(item.Key, out node) && | |
329 EqualityComparer<TValue>.Default.Equals(item.Value, node.Value.V
alue)) | |
330 { | |
331 map.Remove(item.Key); | |
332 node.List.Remove(node); | |
333 return true; | |
334 } | |
335 else | |
336 { | |
337 return false; | |
338 } | |
339 } | |
340 | |
341 /// <summary> | |
342 /// Returns whether or not this map allows values to be null. | |
343 /// </summary> | |
344 public bool AllowsNullValues { get { return allowNullValues; } } | |
345 | |
346 /// <summary> | |
347 /// Gets the number of elements contained in the map. | |
348 /// </summary> | |
349 public int Count { get { return list.Count; } } | |
350 | |
351 /// <summary> | |
352 /// Gets a value indicating whether the map is read-only. | |
353 /// </summary> | |
354 public bool IsReadOnly { get { return false; } } | |
355 | |
356 /// <summary> | |
357 /// Determines whether the specified <see cref="System.Object" />, is eq
ual to this instance. | |
358 /// </summary> | |
359 /// <param name="other">The <see cref="System.Object" /> to compare with
this instance.</param> | |
360 /// <returns> | |
361 /// <c>true</c> if the specified <see cref="System.Object" /> is equal
to this instance; otherwise, <c>false</c>. | |
362 /// </returns> | |
363 public override bool Equals(object other) | |
364 { | |
365 return Equals(other as MapField<TKey, TValue>); | |
366 } | |
367 | |
368 /// <summary> | |
369 /// Returns a hash code for this instance. | |
370 /// </summary> | |
371 /// <returns> | |
372 /// A hash code for this instance, suitable for use in hashing algorithm
s and data structures like a hash table. | |
373 /// </returns> | |
374 public override int GetHashCode() | |
375 { | |
376 var valueComparer = EqualityComparer<TValue>.Default; | |
377 int hash = 0; | |
378 foreach (var pair in list) | |
379 { | |
380 hash ^= pair.Key.GetHashCode() * 31 + valueComparer.GetHashCode(
pair.Value); | |
381 } | |
382 return hash; | |
383 } | |
384 | |
385 /// <summary> | |
386 /// Compares this map with another for equality. | |
387 /// </summary> | |
388 /// <remarks> | |
389 /// The order of the key/value pairs in the maps is not deemed significa
nt in this comparison. | |
390 /// </remarks> | |
391 /// <param name="other">The map to compare this with.</param> | |
392 /// <returns><c>true</c> if <paramref name="other"/> refers to an equal
map; <c>false</c> otherwise.</returns> | |
393 public bool Equals(MapField<TKey, TValue> other) | |
394 { | |
395 if (other == null) | |
396 { | |
397 return false; | |
398 } | |
399 if (other == this) | |
400 { | |
401 return true; | |
402 } | |
403 if (other.Count != this.Count) | |
404 { | |
405 return false; | |
406 } | |
407 var valueComparer = EqualityComparer<TValue>.Default; | |
408 foreach (var pair in this) | |
409 { | |
410 TValue value; | |
411 if (!other.TryGetValue(pair.Key, out value)) | |
412 { | |
413 return false; | |
414 } | |
415 if (!valueComparer.Equals(value, pair.Value)) | |
416 { | |
417 return false; | |
418 } | |
419 } | |
420 return true; | |
421 } | |
422 | |
423 /// <summary> | |
424 /// Adds entries to the map from the given stream. | |
425 /// </summary> | |
426 /// <remarks> | |
427 /// It is assumed that the stream is initially positioned after the tag
specified by the codec. | |
428 /// This method will continue reading entries from the stream until the
end is reached, or | |
429 /// a different tag is encountered. | |
430 /// </remarks> | |
431 /// <param name="input">Stream to read from</param> | |
432 /// <param name="codec">Codec describing how the key/value pairs are enc
oded</param> | |
433 public void AddEntriesFrom(CodedInputStream input, Codec codec) | |
434 { | |
435 var adapter = new Codec.MessageAdapter(codec); | |
436 do | |
437 { | |
438 adapter.Reset(); | |
439 input.ReadMessage(adapter); | |
440 this[adapter.Key] = adapter.Value; | |
441 } while (input.MaybeConsumeTag(codec.MapTag)); | |
442 } | |
443 | |
444 /// <summary> | |
445 /// Writes the contents of this map to the given coded output stream, us
ing the specified codec | |
446 /// to encode each entry. | |
447 /// </summary> | |
448 /// <param name="output">The output stream to write to.</param> | |
449 /// <param name="codec">The codec to use for each entry.</param> | |
450 public void WriteTo(CodedOutputStream output, Codec codec) | |
451 { | |
452 var message = new Codec.MessageAdapter(codec); | |
453 foreach (var entry in list) | |
454 { | |
455 message.Key = entry.Key; | |
456 message.Value = entry.Value; | |
457 output.WriteTag(codec.MapTag); | |
458 output.WriteMessage(message); | |
459 } | |
460 } | |
461 | |
462 /// <summary> | |
463 /// Calculates the size of this map based on the given entry codec. | |
464 /// </summary> | |
465 /// <param name="codec">The codec to use to encode each entry.</param> | |
466 /// <returns></returns> | |
467 public int CalculateSize(Codec codec) | |
468 { | |
469 if (Count == 0) | |
470 { | |
471 return 0; | |
472 } | |
473 var message = new Codec.MessageAdapter(codec); | |
474 int size = 0; | |
475 foreach (var entry in list) | |
476 { | |
477 message.Key = entry.Key; | |
478 message.Value = entry.Value; | |
479 size += CodedOutputStream.ComputeRawVarint32Size(codec.MapTag); | |
480 size += CodedOutputStream.ComputeMessageSize(message); | |
481 } | |
482 return size; | |
483 } | |
484 | |
485 #region IDictionary explicit interface implementation | |
486 void IDictionary.Add(object key, object value) | |
487 { | |
488 Add((TKey)key, (TValue)value); | |
489 } | |
490 | |
491 bool IDictionary.Contains(object key) | |
492 { | |
493 if (!(key is TKey)) | |
494 { | |
495 return false; | |
496 } | |
497 return ContainsKey((TKey)key); | |
498 } | |
499 | |
500 IDictionaryEnumerator IDictionary.GetEnumerator() | |
501 { | |
502 return new DictionaryEnumerator(GetEnumerator()); | |
503 } | |
504 | |
505 void IDictionary.Remove(object key) | |
506 { | |
507 Preconditions.CheckNotNull(key, "key"); | |
508 if (!(key is TKey)) | |
509 { | |
510 return; | |
511 } | |
512 Remove((TKey)key); | |
513 } | |
514 | |
515 void ICollection.CopyTo(Array array, int index) | |
516 { | |
517 // This is ugly and slow as heck, but with any luck it will never be
used anyway. | |
518 ICollection temp = this.Select(pair => new DictionaryEntry(pair.Key,
pair.Value)).ToList(); | |
519 temp.CopyTo(array, index); | |
520 } | |
521 | |
522 bool IDictionary.IsFixedSize { get { return false; } } | |
523 | |
524 ICollection IDictionary.Keys { get { return (ICollection)Keys; } } | |
525 | |
526 ICollection IDictionary.Values { get { return (ICollection)Values; } } | |
527 | |
528 bool ICollection.IsSynchronized { get { return false; } } | |
529 | |
530 object ICollection.SyncRoot { get { return this; } } | |
531 | |
532 object IDictionary.this[object key] | |
533 { | |
534 get | |
535 { | |
536 Preconditions.CheckNotNull(key, "key"); | |
537 if (!(key is TKey)) | |
538 { | |
539 return null; | |
540 } | |
541 TValue value; | |
542 TryGetValue((TKey)key, out value); | |
543 return value; | |
544 } | |
545 | |
546 set | |
547 { | |
548 this[(TKey)key] = (TValue)value; | |
549 } | |
550 } | |
551 #endregion | |
552 | |
553 private class DictionaryEnumerator : IDictionaryEnumerator | |
554 { | |
555 private readonly IEnumerator<KeyValuePair<TKey, TValue>> enumerator; | |
556 | |
557 internal DictionaryEnumerator(IEnumerator<KeyValuePair<TKey, TValue>
> enumerator) | |
558 { | |
559 this.enumerator = enumerator; | |
560 } | |
561 | |
562 public bool MoveNext() | |
563 { | |
564 return enumerator.MoveNext(); | |
565 } | |
566 | |
567 public void Reset() | |
568 { | |
569 enumerator.Reset(); | |
570 } | |
571 | |
572 public object Current { get { return Entry; } } | |
573 public DictionaryEntry Entry { get { return new DictionaryEntry(Key,
Value); } } | |
574 public object Key { get { return enumerator.Current.Key; } } | |
575 public object Value { get { return enumerator.Current.Value; } } | |
576 } | |
577 | |
578 /// <summary> | |
579 /// A codec for a specific map field. This contains all the information
required to encode and | |
580 /// decode the nested messages. | |
581 /// </summary> | |
582 public sealed class Codec | |
583 { | |
584 private readonly FieldCodec<TKey> keyCodec; | |
585 private readonly FieldCodec<TValue> valueCodec; | |
586 private readonly uint mapTag; | |
587 | |
588 /// <summary> | |
589 /// Creates a new entry codec based on a separate key codec and valu
e codec, | |
590 /// and the tag to use for each map entry. | |
591 /// </summary> | |
592 /// <param name="keyCodec">The key codec.</param> | |
593 /// <param name="valueCodec">The value codec.</param> | |
594 /// <param name="mapTag">The map tag to use to introduce each map en
try.</param> | |
595 public Codec(FieldCodec<TKey> keyCodec, FieldCodec<TValue> valueCode
c, uint mapTag) | |
596 { | |
597 this.keyCodec = keyCodec; | |
598 this.valueCodec = valueCodec; | |
599 this.mapTag = mapTag; | |
600 } | |
601 | |
602 /// <summary> | |
603 /// The tag used in the enclosing message to indicate map entries. | |
604 /// </summary> | |
605 internal uint MapTag { get { return mapTag; } } | |
606 | |
607 /// <summary> | |
608 /// A mutable message class, used for parsing and serializing. This | |
609 /// delegates the work to a codec, but implements the <see cref="IMe
ssage"/> interface | |
610 /// for interop with <see cref="CodedInputStream"/> and <see cref="C
odedOutputStream"/>. | |
611 /// This is nested inside Codec as it's tightly coupled to the assoc
iated codec, | |
612 /// and it's simpler if it has direct access to all its fields. | |
613 /// </summary> | |
614 internal class MessageAdapter : IMessage | |
615 { | |
616 private readonly Codec codec; | |
617 internal TKey Key { get; set; } | |
618 internal TValue Value { get; set; } | |
619 | |
620 internal MessageAdapter(Codec codec) | |
621 { | |
622 this.codec = codec; | |
623 } | |
624 | |
625 internal void Reset() | |
626 { | |
627 Key = codec.keyCodec.DefaultValue; | |
628 Value = codec.valueCodec.DefaultValue; | |
629 } | |
630 | |
631 public void MergeFrom(CodedInputStream input) | |
632 { | |
633 uint tag; | |
634 while ((tag = input.ReadTag()) != 0) | |
635 { | |
636 if (tag == codec.keyCodec.Tag) | |
637 { | |
638 Key = codec.keyCodec.Read(input); | |
639 } | |
640 else if (tag == codec.valueCodec.Tag) | |
641 { | |
642 Value = codec.valueCodec.Read(input); | |
643 } | |
644 else | |
645 { | |
646 input.SkipLastField(); | |
647 } | |
648 } | |
649 } | |
650 | |
651 public void WriteTo(CodedOutputStream output) | |
652 { | |
653 codec.keyCodec.WriteTagAndValue(output, Key); | |
654 codec.valueCodec.WriteTagAndValue(output, Value); | |
655 } | |
656 | |
657 public int CalculateSize() | |
658 { | |
659 return codec.keyCodec.CalculateSizeWithTag(Key) + codec.valu
eCodec.CalculateSizeWithTag(Value); | |
660 } | |
661 | |
662 MessageDescriptor IMessage.Descriptor { get { return null; } } | |
663 } | |
664 } | |
665 | |
666 private class MapView<T> : ICollection<T>, ICollection | |
667 { | |
668 private readonly MapField<TKey, TValue> parent; | |
669 private readonly Func<KeyValuePair<TKey, TValue>, T> projection; | |
670 private readonly Func<T, bool> containsCheck; | |
671 | |
672 internal MapView( | |
673 MapField<TKey, TValue> parent, | |
674 Func<KeyValuePair<TKey, TValue>, T> projection, | |
675 Func<T, bool> containsCheck) | |
676 { | |
677 this.parent = parent; | |
678 this.projection = projection; | |
679 this.containsCheck = containsCheck; | |
680 } | |
681 | |
682 public int Count { get { return parent.Count; } } | |
683 | |
684 public bool IsReadOnly { get { return true; } } | |
685 | |
686 public bool IsSynchronized { get { return false; } } | |
687 | |
688 public object SyncRoot { get { return parent; } } | |
689 | |
690 public void Add(T item) | |
691 { | |
692 throw new NotSupportedException(); | |
693 } | |
694 | |
695 public void Clear() | |
696 { | |
697 throw new NotSupportedException(); | |
698 } | |
699 | |
700 public bool Contains(T item) | |
701 { | |
702 return containsCheck(item); | |
703 } | |
704 | |
705 public void CopyTo(T[] array, int arrayIndex) | |
706 { | |
707 if (arrayIndex < 0) | |
708 { | |
709 throw new ArgumentOutOfRangeException("arrayIndex"); | |
710 } | |
711 if (arrayIndex + Count >= array.Length) | |
712 { | |
713 throw new ArgumentException("Not enough space in the array",
"array"); | |
714 } | |
715 foreach (var item in this) | |
716 { | |
717 array[arrayIndex++] = item; | |
718 } | |
719 } | |
720 | |
721 public IEnumerator<T> GetEnumerator() | |
722 { | |
723 return parent.list.Select(projection).GetEnumerator(); | |
724 } | |
725 | |
726 public bool Remove(T item) | |
727 { | |
728 throw new NotSupportedException(); | |
729 } | |
730 | |
731 IEnumerator IEnumerable.GetEnumerator() | |
732 { | |
733 return GetEnumerator(); | |
734 } | |
735 | |
736 public void CopyTo(Array array, int index) | |
737 { | |
738 if (index < 0) | |
739 { | |
740 throw new ArgumentOutOfRangeException("index"); | |
741 } | |
742 if (index + Count >= array.Length) | |
743 { | |
744 throw new ArgumentException("Not enough space in the array",
"array"); | |
745 } | |
746 foreach (var item in this) | |
747 { | |
748 array.SetValue(item, index++); | |
749 } | |
750 } | |
751 } | |
752 } | |
753 } | |
OLD | NEW |