blob: 88b3ec000d31557bcb2932fcf9a29b03c385c62a [file] [log] [blame]
Austin Schuh40c16522018-10-28 20:27:54 -07001#region Copyright notice and license
2// Protocol Buffers - Google's data interchange format
3// Copyright 2017 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.Generic;
35
36namespace Google.Protobuf.Reflection
37{
38 /// <summary>
39 /// Container for a set of custom options specified within a message, field etc.
40 /// </summary>
41 /// <remarks>
42 /// <para>
43 /// This type is publicly immutable, but internally mutable. It is only populated
44 /// by the descriptor parsing code - by the time any user code is able to see an instance,
45 /// it will be fully initialized.
46 /// </para>
47 /// <para>
48 /// If an option is requested using the incorrect method, an answer may still be returned: all
49 /// of the numeric types are represented internally using 64-bit integers, for example. It is up to
50 /// the caller to ensure that they make the appropriate method call for the option they're interested in.
51 /// Note that enum options are simply stored as integers, so the value should be fetched using
52 /// <see cref="TryGetInt32(int, out int)"/> and then cast appropriately.
53 /// </para>
54 /// <para>
55 /// Repeated options are currently not supported. Asking for a single value of an option
56 /// which was actually repeated will return the last value, except for message types where
57 /// all the set values are merged together.
58 /// </para>
59 /// </remarks>
60 public sealed class CustomOptions
61 {
62 /// <summary>
63 /// Singleton for all descriptors with an empty set of options.
64 /// </summary>
65 internal static readonly CustomOptions Empty = new CustomOptions();
66
67 /// <summary>
68 /// A sequence of values per field. This needs to be per field rather than per tag to allow correct deserialization
69 /// of repeated fields which could be "int, ByteString, int" - unlikely as that is. The fact that values are boxed
70 /// is unfortunate; we might be able to use a struct instead, and we could combine uint and ulong values.
71 /// </summary>
72 private readonly Dictionary<int, List<FieldValue>> valuesByField = new Dictionary<int, List<FieldValue>>();
73
74 private CustomOptions() { }
75
76 /// <summary>
77 /// Retrieves a Boolean value for the specified option field.
78 /// </summary>
79 /// <param name="field">The field to fetch the value for.</param>
80 /// <param name="value">The output variable to populate.</param>
81 /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
82 public bool TryGetBool(int field, out bool value)
83 {
84 ulong? tmp = GetLastNumericValue(field);
85 value = tmp == 1UL;
86 return tmp != null;
87 }
88
89 /// <summary>
90 /// Retrieves a signed 32-bit integer value for the specified option field.
91 /// </summary>
92 /// <param name="field">The field to fetch the value for.</param>
93 /// <param name="value">The output variable to populate.</param>
94 /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
95 public bool TryGetInt32(int field, out int value)
96 {
97 ulong? tmp = GetLastNumericValue(field);
98 value = (int) tmp.GetValueOrDefault();
99 return tmp != null;
100 }
101
102 /// <summary>
103 /// Retrieves a signed 64-bit integer value for the specified option field.
104 /// </summary>
105 /// <param name="field">The field to fetch the value for.</param>
106 /// <param name="value">The output variable to populate.</param>
107 /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
108 public bool TryGetInt64(int field, out long value)
109 {
110 ulong? tmp = GetLastNumericValue(field);
111 value = (long) tmp.GetValueOrDefault();
112 return tmp != null;
113 }
114
115 /// <summary>
116 /// Retrieves an unsigned 32-bit integer value for the specified option field,
117 /// assuming a fixed-length representation.
118 /// </summary>
119 /// <param name="field">The field to fetch the value for.</param>
120 /// <param name="value">The output variable to populate.</param>
121 /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
122 public bool TryGetFixed32(int field, out uint value) => TryGetUInt32(field, out value);
123
124 /// <summary>
125 /// Retrieves an unsigned 64-bit integer value for the specified option field,
126 /// assuming a fixed-length representation.
127 /// </summary>
128 /// <param name="field">The field to fetch the value for.</param>
129 /// <param name="value">The output variable to populate.</param>
130 /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
131 public bool TryGetFixed64(int field, out ulong value) => TryGetUInt64(field, out value);
132
133 /// <summary>
134 /// Retrieves a signed 32-bit integer value for the specified option field,
135 /// assuming a fixed-length representation.
136 /// </summary>
137 /// <param name="field">The field to fetch the value for.</param>
138 /// <param name="value">The output variable to populate.</param>
139 /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
140 public bool TryGetSFixed32(int field, out int value) => TryGetInt32(field, out value);
141
142 /// <summary>
143 /// Retrieves a signed 64-bit integer value for the specified option field,
144 /// assuming a fixed-length representation.
145 /// </summary>
146 /// <param name="field">The field to fetch the value for.</param>
147 /// <param name="value">The output variable to populate.</param>
148 /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
149 public bool TryGetSFixed64(int field, out long value) => TryGetInt64(field, out value);
150
151 /// <summary>
152 /// Retrieves a signed 32-bit integer value for the specified option field,
153 /// assuming a zigzag encoding.
154 /// </summary>
155 /// <param name="field">The field to fetch the value for.</param>
156 /// <param name="value">The output variable to populate.</param>
157 /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
158 public bool TryGetSInt32(int field, out int value)
159 {
160 ulong? tmp = GetLastNumericValue(field);
161 value = CodedInputStream.DecodeZigZag32((uint) tmp.GetValueOrDefault());
162 return tmp != null;
163 }
164
165 /// <summary>
166 /// Retrieves a signed 64-bit integer value for the specified option field,
167 /// assuming a zigzag encoding.
168 /// </summary>
169 /// <param name="field">The field to fetch the value for.</param>
170 /// <param name="value">The output variable to populate.</param>
171 /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
172 public bool TryGetSInt64(int field, out long value)
173 {
174 ulong? tmp = GetLastNumericValue(field);
175 value = CodedInputStream.DecodeZigZag64(tmp.GetValueOrDefault());
176 return tmp != null;
177 }
178
179 /// <summary>
180 /// Retrieves an unsigned 32-bit integer value for the specified option field.
181 /// </summary>
182 /// <param name="field">The field to fetch the value for.</param>
183 /// <param name="value">The output variable to populate.</param>
184 /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
185 public bool TryGetUInt32(int field, out uint value)
186 {
187 ulong? tmp = GetLastNumericValue(field);
188 value = (uint) tmp.GetValueOrDefault();
189 return tmp != null;
190 }
191
192 /// <summary>
193 /// Retrieves an unsigned 64-bit integer value for the specified option field.
194 /// </summary>
195 /// <param name="field">The field to fetch the value for.</param>
196 /// <param name="value">The output variable to populate.</param>
197 /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
198 public bool TryGetUInt64(int field, out ulong value)
199 {
200 ulong? tmp = GetLastNumericValue(field);
201 value = tmp.GetValueOrDefault();
202 return tmp != null;
203 }
204
205 /// <summary>
206 /// Retrieves a 32-bit floating point value for the specified option field.
207 /// </summary>
208 /// <param name="field">The field to fetch the value for.</param>
209 /// <param name="value">The output variable to populate.</param>
210 /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
211 public bool TryGetFloat(int field, out float value)
212 {
213 ulong? tmp = GetLastNumericValue(field);
214 int int32 = (int) tmp.GetValueOrDefault();
215 byte[] bytes = BitConverter.GetBytes(int32);
216 value = BitConverter.ToSingle(bytes, 0);
217 return tmp != null;
218 }
219
220 /// <summary>
221 /// Retrieves a 64-bit floating point value for the specified option field.
222 /// </summary>
223 /// <param name="field">The field to fetch the value for.</param>
224 /// <param name="value">The output variable to populate.</param>
225 /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
226 public bool TryGetDouble(int field, out double value)
227 {
228 ulong? tmp = GetLastNumericValue(field);
229 value = BitConverter.Int64BitsToDouble((long) tmp.GetValueOrDefault());
230 return tmp != null;
231 }
232
233 /// <summary>
234 /// Retrieves a string value for the specified option field.
235 /// </summary>
236 /// <param name="field">The field to fetch the value for.</param>
237 /// <param name="value">The output variable to populate.</param>
238 /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
239 public bool TryGetString(int field, out string value)
240 {
241 ByteString bytes = GetLastByteStringValue(field);
242 value = bytes?.ToStringUtf8();
243 return bytes != null;
244 }
245
246 /// <summary>
247 /// Retrieves a bytes value for the specified option field.
248 /// </summary>
249 /// <param name="field">The field to fetch the value for.</param>
250 /// <param name="value">The output variable to populate.</param>
251 /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
252 public bool TryGetBytes(int field, out ByteString value)
253 {
254 ByteString bytes = GetLastByteStringValue(field);
255 value = bytes;
256 return bytes != null;
257 }
258
259 /// <summary>
260 /// Retrieves a message value for the specified option field.
261 /// </summary>
262 /// <param name="field">The field to fetch the value for.</param>
263 /// <param name="value">The output variable to populate.</param>
264 /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
265 public bool TryGetMessage<T>(int field, out T value) where T : class, IMessage, new()
266 {
267 value = null;
268 List<FieldValue> values;
269 if (!valuesByField.TryGetValue(field, out values))
270 {
271 return false;
272 }
273 foreach (FieldValue fieldValue in values)
274 {
275 if (fieldValue.ByteString != null)
276 {
277 if (value == null)
278 {
279 value = new T();
280 }
281 value.MergeFrom(fieldValue.ByteString);
282 }
283 }
284 return value != null;
285 }
286
287 private ulong? GetLastNumericValue(int field)
288 {
289 List<FieldValue> values;
290 if (!valuesByField.TryGetValue(field, out values))
291 {
292 return null;
293 }
294 for (int i = values.Count - 1; i >= 0; i--)
295 {
296 // A non-bytestring value is a numeric value
297 if (values[i].ByteString == null)
298 {
299 return values[i].Number;
300 }
301 }
302 return null;
303 }
304
305 private ByteString GetLastByteStringValue(int field)
306 {
307 List<FieldValue> values;
308 if (!valuesByField.TryGetValue(field, out values))
309 {
310 return null;
311 }
312 for (int i = values.Count - 1; i >= 0; i--)
313 {
314 if (values[i].ByteString != null)
315 {
316 return values[i].ByteString;
317 }
318 }
319 return null;
320 }
321
322 /// <summary>
323 /// Reads an unknown field, either parsing it and storing it or skipping it.
324 /// </summary>
325 /// <remarks>
326 /// If the current set of options is empty and we manage to read a field, a new set of options
327 /// will be created and returned. Otherwise, the return value is <c>this</c>. This allows
328 /// us to start with a singleton empty set of options and just create new ones where necessary.
329 /// </remarks>
330 /// <param name="input">Input stream to read from. </param>
331 /// <returns>The resulting set of custom options, either <c>this</c> or a new set.</returns>
332 internal CustomOptions ReadOrSkipUnknownField(CodedInputStream input)
333 {
334 var tag = input.LastTag;
335 var field = WireFormat.GetTagFieldNumber(tag);
336 switch (WireFormat.GetTagWireType(tag))
337 {
338 case WireFormat.WireType.LengthDelimited:
339 return AddValue(field, new FieldValue(input.ReadBytes()));
340 case WireFormat.WireType.Fixed32:
341 return AddValue(field, new FieldValue(input.ReadFixed32()));
342 case WireFormat.WireType.Fixed64:
343 return AddValue(field, new FieldValue(input.ReadFixed64()));
344 case WireFormat.WireType.Varint:
345 return AddValue(field, new FieldValue(input.ReadRawVarint64()));
346 // For StartGroup, EndGroup or any wire format we don't understand,
347 // just use the normal behavior (call SkipLastField).
348 default:
349 input.SkipLastField();
350 return this;
351 }
352 }
353
354 private CustomOptions AddValue(int field, FieldValue value)
355 {
356 var ret = valuesByField.Count == 0 ? new CustomOptions() : this;
357 List<FieldValue> valuesForField;
358 if (!ret.valuesByField.TryGetValue(field, out valuesForField))
359 {
360 // Expect almost all
361 valuesForField = new List<FieldValue>(1);
362 ret.valuesByField[field] = valuesForField;
363 }
364 valuesForField.Add(value);
365 return ret;
366 }
367
368 /// <summary>
369 /// All field values can be stored as a byte string or a 64-bit integer.
370 /// This struct avoids unnecessary boxing.
371 /// </summary>
372 private struct FieldValue
373 {
374 internal ulong Number { get; }
375 internal ByteString ByteString { get; }
376
377 internal FieldValue(ulong number)
378 {
379 Number = number;
380 ByteString = null;
381 }
382
383 internal FieldValue(ByteString byteString)
384 {
385 Number = 0;
386 ByteString = byteString;
387 }
388 }
389 }
390}