Imported Upstream version 3.8.0
[platform/upstream/protobuf.git] / csharp / src / Google.Protobuf / Reflection / CustomOptions.cs
1 #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
33 using System;
34 using System.Collections.Generic;
35
36 namespace 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 }