blob: 913dede276d71c6943f3ea14fc813c83803565ea [file] [log] [blame]
Jon Skeet60c059b2008-10-23 21:17:56 +01001// Protocol Buffers - Google's data interchange format
2// Copyright 2008 Google Inc. All rights reserved.
3// http://github.com/jskeet/dotnet-protobufs/
4// Original C++/Java/Python code:
Jon Skeet68036862008-10-22 13:30:34 +01005// http://code.google.com/p/protobuf/
6//
Jon Skeet60c059b2008-10-23 21:17:56 +01007// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are
9// met:
Jon Skeet68036862008-10-22 13:30:34 +010010//
Jon Skeet60c059b2008-10-23 21:17:56 +010011// * Redistributions of source code must retain the above copyright
12// notice, this list of conditions and the following disclaimer.
13// * Redistributions in binary form must reproduce the above
14// copyright notice, this list of conditions and the following disclaimer
15// in the documentation and/or other materials provided with the
16// distribution.
17// * Neither the name of Google Inc. nor the names of its
18// contributors may be used to endorse or promote products derived from
19// this software without specific prior written permission.
Jon Skeet68036862008-10-22 13:30:34 +010020//
Jon Skeet60c059b2008-10-23 21:17:56 +010021// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Jon Skeet68036862008-10-22 13:30:34 +010032using System;
33using System.Collections;
34using System.Collections.Generic;
35using Google.ProtocolBuffers.Collections;
36using Google.ProtocolBuffers.Descriptors;
37
38namespace Google.ProtocolBuffers {
39 /// <summary>
40 /// A class which represents an arbitrary set of fields of some message type.
41 /// This is used to implement DynamicMessage, and also to represent extensions
42 /// in GeneratedMessage. This class is internal, since outside users should probably
43 /// be using DynamicMessage.
44 ///
45 /// As in the Java implementation, this class goes against the rest of the framework
46 /// in terms of mutability. Instead of having a mutable Builder class and an immutable
47 /// FieldSet class, FieldSet just has a MakeImmutable() method. This is safe so long as
48 /// all callers are careful not to let a mutable FieldSet escape into the open. This would
49 /// be impossible to guarantee if this were a public class, of course.
50 ///
51 /// All repeated fields are stored as IList[object] even
52 /// TODO(jonskeet): Finish this comment!
53 /// </summary>
54 internal sealed class FieldSet {
55
56 private static readonly FieldSet defaultInstance = new FieldSet(new Dictionary<FieldDescriptor, object>()).MakeImmutable();
57
58 private IDictionary<FieldDescriptor, object> fields;
59
60 private FieldSet(IDictionary<FieldDescriptor, object> fields) {
61 this.fields = fields;
62 }
63
64 public static FieldSet CreateInstance() {
65 // Use SortedList to keep fields in the canonical order
66 return new FieldSet(new SortedList<FieldDescriptor, object>());
67 }
68
69 /// <summary>
70 /// Makes this FieldSet immutable, and returns it for convenience. Any
71 /// mutable repeated fields are made immutable, as well as the map itself.
72 /// </summary>
73 internal FieldSet MakeImmutable() {
74 // First check if we have any repeated values
75 bool hasRepeats = false;
76 foreach (object value in fields.Values) {
77 IList<object> list = value as IList<object>;
78 if (list != null && !list.IsReadOnly) {
79 hasRepeats = true;
80 break;
81 }
82 }
83
84 if (hasRepeats) {
85 var tmp = new SortedList<FieldDescriptor, object>();
86 foreach (KeyValuePair<FieldDescriptor, object> entry in fields) {
87 IList<object> list = entry.Value as IList<object>;
88 tmp[entry.Key] = list == null ? entry.Value : Lists.AsReadOnly(list);
89 }
90 fields = tmp;
91 }
92
93 fields = Dictionaries.AsReadOnly(fields);
94
95 return this;
96 }
97
98 /// <summary>
99 /// Returns the default, immutable instance with no fields defined.
100 /// </summary>
101 internal static FieldSet DefaultInstance {
102 get { return defaultInstance; }
103 }
104
105 /// <summary>
106 /// Returns an immutable mapping of fields. Note that although the mapping itself
107 /// is immutable, the entries may not be (i.e. any repeated values are represented by
108 /// mutable lists). The behaviour is not specified if the contents are mutated.
109 /// </summary>
110 internal IDictionary<FieldDescriptor, object> AllFields {
111 get { return Dictionaries.AsReadOnly(fields); }
112 }
113
114 /// <summary>
115 /// See <see cref="IMessage.HasField"/>.
116 /// </summary>
117 public bool HasField(FieldDescriptor field) {
118 if (field.IsRepeated) {
119 throw new ArgumentException("HasField() can only be called on non-repeated fields.");
120 }
121
122 return fields.ContainsKey(field);
123 }
124
125 /// <summary>
126 /// Clears all fields.
127 /// </summary>
128 internal void Clear() {
129 fields.Clear();
130 }
131
132 /// <summary>
133 /// See <see cref="IMessage.Item(FieldDescriptor)"/>
134 /// </summary>
135 /// <remarks>
136 /// If the field is not set, the behaviour when fetching this property varies by field type:
137 /// <list>
138 /// <item>For singular message values, null is returned.</item>
139 /// <item>For singular non-message values, the default value of the field is returned.</item>
140 /// <item>For repeated values, an empty immutable list is returned. This will be compatible
141 /// with IList[object], regardless of the type of the repeated item.</item>
142 /// </list>
143 /// This method returns null if the field is a singular message type
144 /// and is not set; in this case it is up to the caller to fetch the
145 /// message's default instance. For repeated fields of message types,
146 /// an empty collection is returned. For repeated fields of non-message
147 /// types, null is returned.
148 /// <para />
149 /// When setting this property, any list values are copied, and each element is checked
150 /// to ensure it is of an appropriate type.
151 /// </remarks>
152 ///
153 internal object this[FieldDescriptor field] {
154 get {
155 object result;
156 if (fields.TryGetValue(field, out result)) {
157 return result;
158 }
159 if (field.MappedType == MappedType.Message) {
160 if (field.IsRepeated) {
161 return new List<object>();
162 } else {
163 return null;
164 }
165 }
166 return field.DefaultValue;
167 }
168 set {
169 if (field.IsRepeated) {
170 List<object> list = value as List<object>;
171 if (list == null) {
172 throw new ArgumentException("Wrong object type used with protocol message reflection.");
173 }
174
175 // Wrap the contents in a new list so that the caller cannot change
176 // the list's contents after setting it.
177 List<object> newList = new List<object>(list);
178 foreach (object element in newList) {
179 VerifyType(field, element);
180 }
181 value = newList;
182 }
183 else {
184 VerifyType(field, value);
185 }
186 fields[field] = value;
187 }
188 }
189
190 /// <summary>
191 /// See <see cref="IMessage.Item(FieldDescriptor,int)" />
192 /// </summary>
193 internal object this[FieldDescriptor field, int index] {
194 get {
195 if (!field.IsRepeated) {
196 throw new ArgumentException("Indexer specifying field and index can only be called on repeated fields.");
197 }
198
199 return ((IList<object>) this[field])[index];
200 }
201 set {
202 if (!field.IsRepeated) {
203 throw new ArgumentException("Indexer specifying field and index can only be called on repeated fields.");
204 }
205 VerifyType(field, value);
206 object list;
207 if (!fields.TryGetValue(field, out list)) {
208 throw new ArgumentOutOfRangeException();
209 }
210 ((IList<object>) list)[index] = value;
211 }
212 }
213
214 /// <summary>
215 /// See <see cref="IBuilder{TMessage, TBuilder}.AddRepeatedField" />
216 /// </summary>
217 internal void AddRepeatedField(FieldDescriptor field, object value) {
218 if (!field.IsRepeated) {
219 throw new ArgumentException("AddRepeatedField can only be called on repeated fields.");
220 }
221 VerifyType(field, value);
222 object list;
223 if (!fields.TryGetValue(field, out list)) {
224 list = new List<object>();
225 fields[field] = list;
226 }
227 ((IList<object>) list).Add(value);
228 }
229
230 /// <summary>
231 /// Returns an enumerator for the field map. Used to write the fields out.
232 /// </summary>
233 internal IEnumerator<KeyValuePair<FieldDescriptor, object>> GetEnumerator() {
234 return fields.GetEnumerator();
235 }
236
237 /// <summary>
238 /// See <see cref="IMessage.IsInitialized" />
239 /// </summary>
240 /// <remarks>
241 /// Since FieldSet itself does not have any way of knowing about
242 /// required fields that aren't actually present in the set, it is up
243 /// to the caller to check for genuinely required fields. This property
244 /// merely checks that any messages present are themselves initialized.
245 /// </remarks>
246 internal bool IsInitialized {
247 get {
248 foreach (KeyValuePair<FieldDescriptor, object> entry in fields) {
249 FieldDescriptor field = entry.Key;
250 if (field.MappedType == MappedType.Message) {
251 if (field.IsRepeated) {
252 foreach(IMessage message in (IEnumerable) entry.Value) {
253 if (!message.IsInitialized) {
254 return false;
255 }
256 }
257 } else {
258 if (!((IMessage) entry.Value).IsInitialized) {
259 return false;
260 }
261 }
262 }
263 }
264 return true;
265 }
266 }
267
268 /// <summary>
269 /// Verifies whether all the required fields in the specified message
270 /// descriptor are present in this field set, as well as whether
271 /// all the embedded messages are themselves initialized.
272 /// </summary>
273 internal bool IsInitializedWithRespectTo(MessageDescriptor type) {
274 foreach (FieldDescriptor field in type.Fields) {
275 if (field.IsRequired && !HasField(field)) {
276 return false;
277 }
278 }
279 return IsInitialized;
280 }
281
282 /// <summary>
283 /// See <see cref="IBuilder{TMessage, TBuilder}.ClearField" />
284 /// </summary>
285 public void ClearField(FieldDescriptor field) {
286 fields.Remove(field);
287 }
288
289 /// <summary>
290 /// See <see cref="IMessage.GetRepeatedFieldCount" />
291 /// </summary>
292 public int GetRepeatedFieldCount(FieldDescriptor field) {
293 if (!field.IsRepeated) {
294 throw new ArgumentException("GetRepeatedFieldCount() can only be called on repeated fields.");
295 }
296
297 return ((IList<object>) this[field]).Count;
298 }
299
300 /// <summary>
301 /// Implementation of both <c>MergeFrom</c> methods.
302 /// </summary>
303 /// <param name="otherFields"></param>
304 private void MergeFields(IEnumerable<KeyValuePair<FieldDescriptor, object>> otherFields) {
305 // Note: We don't attempt to verify that other's fields have valid
306 // types. Doing so would be a losing battle. We'd have to verify
307 // all sub-messages as well, and we'd have to make copies of all of
308 // them to insure that they don't change after verification (since
309 // the IMessage interface itself cannot enforce immutability of
310 // implementations).
311 // TODO(jonskeet): Provide a function somewhere called MakeDeepCopy()
312 // which allows people to make secure deep copies of messages.
313
314 foreach (KeyValuePair<FieldDescriptor, object> entry in otherFields) {
315 FieldDescriptor field = entry.Key;
316 object existingValue;
317 fields.TryGetValue(field, out existingValue);
318 if (field.IsRepeated) {
319 if (existingValue == null) {
320 existingValue = new List<object>();
321 fields[field] = existingValue;
322 }
323 IList<object> list = (IList<object>) existingValue;
324 foreach (object otherValue in (IEnumerable) entry.Value) {
325 list.Add(otherValue);
326 }
327 } else if (field.MappedType == MappedType.Message && existingValue != null) {
328 IMessage existingMessage = (IMessage)existingValue;
329 IMessage merged = existingMessage.WeakCreateBuilderForType()
330 .WeakMergeFrom(existingMessage)
331 .WeakMergeFrom((IMessage) entry.Value)
332 .WeakBuild();
333 this[field] = merged;
334 } else {
335 this[field] = entry.Value;
336 }
337 }
338 }
339
340 /// <summary>
341 /// See <see cref="IBuilder{TMessage, TBuilder}.MergeFrom(IMessage)" />
342 /// </summary>
343 public void MergeFrom(IMessage other) {
344 MergeFields(other.AllFields);
345 }
346
347 /// <summary>
348 /// Like <see cref="MergeFrom(IMessage)"/>, but merges from another <c>FieldSet</c>.
349 /// </summary>
350 public void MergeFrom(FieldSet other) {
351 MergeFields(other.fields);
352 }
353
354 /// <summary>
355 /// See <see cref="IMessage.WriteTo(CodedOutputStream)" />.
356 /// </summary>
357 public void WriteTo(CodedOutputStream output) {
358 foreach (KeyValuePair<FieldDescriptor, object> entry in fields) {
359 WriteField(entry.Key, entry.Value, output);
360 }
361 }
362
363 /// <summary>
364 /// Writes a single field to a CodedOutputStream.
365 /// </summary>
366 public void WriteField(FieldDescriptor field, Object value, CodedOutputStream output) {
367 if (field.IsExtension && field.ContainingType.Options.MessageSetWireFormat) {
368 output.WriteMessageSetExtension(field.FieldNumber, (IMessage) value);
369 } else {
370 if (field.IsRepeated) {
Jon Skeet25a28582009-02-18 16:06:22 +0000371 IEnumerable valueList = (IEnumerable) value;
372 if (field.IsPacked) {
373 output.WriteTag(field.FieldNumber, WireFormat.WireType.LengthDelimited);
374 // Compute the total data size so the length can be written.
375 int dataSize = 0;
376 foreach (object element in valueList) {
377 dataSize += CodedOutputStream.ComputeFieldSizeNoTag(field.FieldType, element);
378 }
379 output.WriteRawVarint32((uint)dataSize);
380 // Write the data itself, without any tags.
381 foreach (object element in valueList) {
382 output.WriteFieldNoTag(field.FieldType, element);
383 }
384 } else {
385 foreach (object element in valueList) {
386 output.WriteField(field.FieldType, field.FieldNumber, element);
387 }
Jon Skeet68036862008-10-22 13:30:34 +0100388 }
389 } else {
390 output.WriteField(field.FieldType, field.FieldNumber, value);
391 }
392 }
393 }
394
395 /// <summary>
396 /// See <see cref="IMessage.SerializedSize" />. It's up to the caller to
397 /// cache the resulting size if desired.
398 /// </summary>
399 public int SerializedSize {
400 get {
401 int size = 0;
402 foreach (KeyValuePair<FieldDescriptor, object> entry in fields) {
403 FieldDescriptor field = entry.Key;
404 object value = entry.Value;
405
406 if (field.IsExtension && field.ContainingType.Options.MessageSetWireFormat) {
Jon Skeet25a28582009-02-18 16:06:22 +0000407 size += CodedOutputStream.ComputeMessageSetExtensionSize(field.FieldNumber, (IMessage)value);
Jon Skeet68036862008-10-22 13:30:34 +0100408 } else {
409 if (field.IsRepeated) {
Jon Skeet25a28582009-02-18 16:06:22 +0000410 IEnumerable valueList = (IEnumerable)value;
411 if (field.IsPacked) {
412 int dataSize = 0;
413 foreach (object element in valueList) {
414 dataSize += CodedOutputStream.ComputeFieldSizeNoTag(field.FieldType, element);
415 }
416 size += dataSize + CodedOutputStream.ComputeTagSize(field.FieldNumber) + CodedOutputStream.ComputeRawVarint32Size((uint)dataSize);
417 } else {
418 foreach (object element in valueList) {
419 size += CodedOutputStream.ComputeFieldSize(field.FieldType, field.FieldNumber, element);
420 }
Jon Skeet68036862008-10-22 13:30:34 +0100421 }
422 } else {
423 size += CodedOutputStream.ComputeFieldSize(field.FieldType, field.FieldNumber, value);
424 }
425 }
426 }
427 return size;
428 }
429 }
430
431 /// <summary>
432 /// Verifies that the given object is of the correct type to be a valid
433 /// value for the given field.
434 /// </summary>
435 /// <remarks>
436 /// For repeated fields, this checks if the object is of the right
437 /// element type, not whether it's a list.
438 /// </remarks>
439 /// <exception cref="ArgumentException">The value is not of the right type.</exception>
Jon Skeet642a8142009-01-27 12:25:21 +0000440 /// <exception cref="ArgumentNullException">The value is null.</exception>
Jon Skeet68036862008-10-22 13:30:34 +0100441 private static void VerifyType(FieldDescriptor field, object value) {
Jon Skeet642a8142009-01-27 12:25:21 +0000442 ThrowHelper.ThrowIfNull(value, "value");
Jon Skeet68036862008-10-22 13:30:34 +0100443 bool isValid = false;
444 switch (field.MappedType) {
445 case MappedType.Int32: isValid = value is int; break;
446 case MappedType.Int64: isValid = value is long; break;
447 case MappedType.UInt32: isValid = value is uint; break;
448 case MappedType.UInt64: isValid = value is ulong; break;
449 case MappedType.Single: isValid = value is float; break;
450 case MappedType.Double: isValid = value is double; break;
451 case MappedType.Boolean: isValid = value is bool; break;
452 case MappedType.String: isValid = value is string; break;
453 case MappedType.ByteString: isValid = value is ByteString; break;
454 case MappedType.Enum:
455 EnumValueDescriptor enumValue = value as EnumValueDescriptor;
456 isValid = enumValue != null && enumValue.EnumDescriptor == field.EnumType;
457 break;
458 case MappedType.Message:
459 IMessage messageValue = value as IMessage;
460 isValid = messageValue != null && messageValue.DescriptorForType == field.MessageType;
461 break;
462 }
463
464 if (!isValid) {
465 // When chaining calls to SetField(), it can be hard to tell from
466 // the stack trace which exact call failed, since the whole chain is
467 // considered one line of code. So, let's make sure to include the
468 // field name and other useful info in the exception.
469 throw new ArgumentException("Wrong object type used with protocol message reflection. "
470 + "Message type \"" + field.ContainingType.FullName
471 + "\", field \"" + (field.IsExtension ? field.FullName : field.Name)
472 + "\", value was type \"" + value.GetType().Name + "\".");
473 }
474 }
475 }
476}