• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #region Copyright notice and license
2 // Protocol Buffers - Google's data interchange format
3 // Copyright 2017 Google Inc.  All rights reserved.
4 //
5 // Use of this source code is governed by a BSD-style
6 // license that can be found in the LICENSE file or at
7 // https://developers.google.com/open-source/licenses/bsd
8 #endregion
9 
10 using Google.Protobuf.Collections;
11 using System;
12 using System.Collections;
13 using System.Collections.Generic;
14 using System.Diagnostics;
15 using System.Diagnostics.CodeAnalysis;
16 using System.Linq;
17 using System.Reflection;
18 
19 namespace Google.Protobuf.Reflection
20 {
21     /// <summary>
22     /// Container for a set of custom options specified within a message, field etc.
23     /// </summary>
24     /// <remarks>
25     /// <para>
26     /// This type is publicly immutable, but internally mutable. It is only populated
27     /// by the descriptor parsing code - by the time any user code is able to see an instance,
28     /// it will be fully initialized.
29     /// </para>
30     /// <para>
31     /// If an option is requested using the incorrect method, an answer may still be returned: all
32     /// of the numeric types are represented internally using 64-bit integers, for example. It is up to
33     /// the caller to ensure that they make the appropriate method call for the option they're interested in.
34     /// Note that enum options are simply stored as integers, so the value should be fetched using
35     /// <see cref="TryGetInt32(int, out int)"/> and then cast appropriately.
36     /// </para>
37     /// <para>
38     /// Repeated options are currently not supported. Asking for a single value of an option
39     /// which was actually repeated will return the last value, except for message types where
40     /// all the set values are merged together.
41     /// </para>
42     /// </remarks>
43     [DebuggerDisplay("Count = {DebugCount}")]
44     [DebuggerTypeProxy(typeof(CustomOptionsDebugView))]
45     public sealed class CustomOptions
46     {
47         private const string UnreferencedCodeMessage = "CustomOptions is incompatible with trimming.";
48 
49         private static readonly object[] EmptyParameters = new object[0];
50         private readonly IDictionary<int, IExtensionValue> values;
51 
CustomOptions(IDictionary<int, IExtensionValue> values)52         internal CustomOptions(IDictionary<int, IExtensionValue> values)
53         {
54             this.values = values;
55         }
56 
57         /// <summary>
58         /// Retrieves a Boolean value for the specified option field.
59         /// </summary>
60         /// <param name="field">The field to fetch the value for.</param>
61         /// <param name="value">The output variable to populate.</param>
62         /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
63         [RequiresUnreferencedCode(UnreferencedCodeMessage)]
TryGetBool(int field, out bool value)64         public bool TryGetBool(int field, out bool value) => TryGetPrimitiveValue(field, out value);
65 
66         /// <summary>
67         /// Retrieves a signed 32-bit integer value for the specified option field.
68         /// </summary>
69         /// <param name="field">The field to fetch the value for.</param>
70         /// <param name="value">The output variable to populate.</param>
71         /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
72         [RequiresUnreferencedCode(UnreferencedCodeMessage)]
TryGetInt32(int field, out int value)73         public bool TryGetInt32(int field, out int value) => TryGetPrimitiveValue(field, out value);
74 
75         /// <summary>
76         /// Retrieves a signed 64-bit integer value for the specified option field.
77         /// </summary>
78         /// <param name="field">The field to fetch the value for.</param>
79         /// <param name="value">The output variable to populate.</param>
80         /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
81         [RequiresUnreferencedCode(UnreferencedCodeMessage)]
TryGetInt64(int field, out long value)82         public bool TryGetInt64(int field, out long value) => TryGetPrimitiveValue(field, out value);
83 
84         /// <summary>
85         /// Retrieves an unsigned 32-bit integer value for the specified option field,
86         /// assuming a fixed-length representation.
87         /// </summary>
88         /// <param name="field">The field to fetch the value for.</param>
89         /// <param name="value">The output variable to populate.</param>
90         /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
91         [RequiresUnreferencedCode(UnreferencedCodeMessage)]
TryGetFixed32(int field, out uint value)92         public bool TryGetFixed32(int field, out uint value) => TryGetUInt32(field, out value);
93 
94         /// <summary>
95         /// Retrieves an unsigned 64-bit integer value for the specified option field,
96         /// assuming a fixed-length representation.
97         /// </summary>
98         /// <param name="field">The field to fetch the value for.</param>
99         /// <param name="value">The output variable to populate.</param>
100         /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
101         [RequiresUnreferencedCode(UnreferencedCodeMessage)]
TryGetFixed64(int field, out ulong value)102         public bool TryGetFixed64(int field, out ulong value) => TryGetUInt64(field, out value);
103 
104         /// <summary>
105         /// Retrieves a signed 32-bit integer value for the specified option field,
106         /// assuming a fixed-length representation.
107         /// </summary>
108         /// <param name="field">The field to fetch the value for.</param>
109         /// <param name="value">The output variable to populate.</param>
110         /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
111         [RequiresUnreferencedCode(UnreferencedCodeMessage)]
TryGetSFixed32(int field, out int value)112         public bool TryGetSFixed32(int field, out int value) => TryGetInt32(field, out value);
113 
114         /// <summary>
115         /// Retrieves a signed 64-bit integer value for the specified option field,
116         /// assuming a fixed-length representation.
117         /// </summary>
118         /// <param name="field">The field to fetch the value for.</param>
119         /// <param name="value">The output variable to populate.</param>
120         /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
121         [RequiresUnreferencedCode(UnreferencedCodeMessage)]
TryGetSFixed64(int field, out long value)122         public bool TryGetSFixed64(int field, out long value) => TryGetInt64(field, out value);
123 
124         /// <summary>
125         /// Retrieves a signed 32-bit integer value for the specified option field,
126         /// assuming a zigzag encoding.
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         [RequiresUnreferencedCode(UnreferencedCodeMessage)]
TryGetSInt32(int field, out int value)132         public bool TryGetSInt32(int field, out int value) => TryGetPrimitiveValue(field, out value);
133 
134         /// <summary>
135         /// Retrieves a signed 64-bit integer value for the specified option field,
136         /// assuming a zigzag encoding.
137         /// </summary>
138         /// <param name="field">The field to fetch the value for.</param>
139         /// <param name="value">The output variable to populate.</param>
140         /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
141         [RequiresUnreferencedCode(UnreferencedCodeMessage)]
TryGetSInt64(int field, out long value)142         public bool TryGetSInt64(int field, out long value) => TryGetPrimitiveValue(field, out value);
143 
144         /// <summary>
145         /// Retrieves an unsigned 32-bit integer value for the specified option field.
146         /// </summary>
147         /// <param name="field">The field to fetch the value for.</param>
148         /// <param name="value">The output variable to populate.</param>
149         /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
150         [RequiresUnreferencedCode(UnreferencedCodeMessage)]
TryGetUInt32(int field, out uint value)151         public bool TryGetUInt32(int field, out uint value) => TryGetPrimitiveValue(field, out value);
152 
153         /// <summary>
154         /// Retrieves an unsigned 64-bit integer value for the specified option field.
155         /// </summary>
156         /// <param name="field">The field to fetch the value for.</param>
157         /// <param name="value">The output variable to populate.</param>
158         /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
159         [RequiresUnreferencedCode(UnreferencedCodeMessage)]
TryGetUInt64(int field, out ulong value)160         public bool TryGetUInt64(int field, out ulong value) => TryGetPrimitiveValue(field, out value);
161 
162         /// <summary>
163         /// Retrieves a 32-bit floating point value for the specified option field.
164         /// </summary>
165         /// <param name="field">The field to fetch the value for.</param>
166         /// <param name="value">The output variable to populate.</param>
167         /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
168         [RequiresUnreferencedCode(UnreferencedCodeMessage)]
TryGetFloat(int field, out float value)169         public bool TryGetFloat(int field, out float value) => TryGetPrimitiveValue(field, out value);
170 
171         /// <summary>
172         /// Retrieves a 64-bit floating point value for the specified option field.
173         /// </summary>
174         /// <param name="field">The field to fetch the value for.</param>
175         /// <param name="value">The output variable to populate.</param>
176         /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
177         [RequiresUnreferencedCode(UnreferencedCodeMessage)]
TryGetDouble(int field, out double value)178         public bool TryGetDouble(int field, out double value) => TryGetPrimitiveValue(field, out value);
179 
180         /// <summary>
181         /// Retrieves a string value for the specified option field.
182         /// </summary>
183         /// <param name="field">The field to fetch the value for.</param>
184         /// <param name="value">The output variable to populate.</param>
185         /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
186         [RequiresUnreferencedCode(UnreferencedCodeMessage)]
TryGetString(int field, out string value)187         public bool TryGetString(int field, out string value) => TryGetPrimitiveValue(field, out value);
188 
189         /// <summary>
190         /// Retrieves a bytes value for the specified option field.
191         /// </summary>
192         /// <param name="field">The field to fetch the value for.</param>
193         /// <param name="value">The output variable to populate.</param>
194         /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
195         [RequiresUnreferencedCode(UnreferencedCodeMessage)]
TryGetBytes(int field, out ByteString value)196         public bool TryGetBytes(int field, out ByteString value) => TryGetPrimitiveValue(field, out value);
197 
198         /// <summary>
199         /// Retrieves a message value for the specified option field.
200         /// </summary>
201         /// <param name="field">The field to fetch the value for.</param>
202         /// <param name="value">The output variable to populate.</param>
203         /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns>
204         [RequiresUnreferencedCode(UnreferencedCodeMessage)]
205         public bool TryGetMessage<T>(int field, out T value) where T : class, IMessage, new()
206         {
207             if (values == null)
208             {
209                 value = default;
210                 return false;
211             }
212 
213             if (values.TryGetValue(field, out IExtensionValue extensionValue))
214             {
215                 if (extensionValue is ExtensionValue<T> single)
216                 {
217                     ByteString bytes = single.GetValue().ToByteString();
218                     value = new T();
219                     value.MergeFrom(bytes);
220                     return true;
221                 }
222                 else if (extensionValue is RepeatedExtensionValue<T> repeated)
223                 {
224                     value = repeated.GetValue()
225                         .Select(v => v.ToByteString())
226                         .Aggregate(new T(), (t, b) =>
227                         {
228                             t.MergeFrom(b);
229                             return t;
230                         });
231                     return true;
232                 }
233             }
234 
235             value = null;
236             return false;
237         }
238 
239         [RequiresUnreferencedCode(UnreferencedCodeMessage)]
TryGetPrimitiveValue(int field, out T value)240         private bool TryGetPrimitiveValue<T>(int field, out T value)
241         {
242             if (values == null)
243             {
244                 value = default;
245                 return false;
246             }
247 
248             if (values.TryGetValue(field, out IExtensionValue extensionValue))
249             {
250                 if (extensionValue is ExtensionValue<T> single)
251                 {
252                     value = single.GetValue();
253                     return true;
254                 }
255                 else if (extensionValue is RepeatedExtensionValue<T> repeated)
256                 {
257                     if (repeated.GetValue().Count != 0)
258                     {
259                         RepeatedField<T> repeatedField = repeated.GetValue();
260                         value = repeatedField[repeatedField.Count - 1];
261                         return true;
262                     }
263                 }
264                 else // and here we find explicit enum handling since T : Enum ! x is ExtensionValue<Enum>
265                 {
266                     var type = extensionValue.GetType();
267                     if (type.GetGenericTypeDefinition() == typeof(ExtensionValue<>))
268                     {
269                         var typeInfo = type.GetTypeInfo();
270                         var typeArgs = typeInfo.GenericTypeArguments;
271                         if (typeArgs.Length == 1 && typeArgs[0].GetTypeInfo().IsEnum)
272                         {
273                             value = (T)typeInfo.GetDeclaredMethod(nameof(ExtensionValue<T>.GetValue)).Invoke(extensionValue, EmptyParameters);
274                             return true;
275                         }
276                     }
277                     else if (type.GetGenericTypeDefinition() == typeof(RepeatedExtensionValue<>))
278                     {
279                         var typeInfo = type.GetTypeInfo();
280                         var typeArgs = typeInfo.GenericTypeArguments;
281                         if (typeArgs.Length == 1 && typeArgs[0].GetTypeInfo().IsEnum)
282                         {
283                             var values = (IList)typeInfo.GetDeclaredMethod(nameof(RepeatedExtensionValue<T>.GetValue)).Invoke(extensionValue, EmptyParameters);
284                             if (values.Count != 0)
285                             {
286                                 value = (T)values[values.Count - 1];
287                                 return true;
288                             }
289                         }
290                     }
291                 }
292             }
293 
294             value = default;
295             return false;
296         }
297 
298         private int DebugCount => values?.Count ?? 0;
299 
300         private sealed class CustomOptionsDebugView
301         {
302             private readonly CustomOptions customOptions;
303 
CustomOptionsDebugView(CustomOptions customOptions)304             public CustomOptionsDebugView(CustomOptions customOptions)
305             {
306                 this.customOptions = customOptions;
307             }
308 
309             [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
310             public KeyValuePair<int, IExtensionValue>[] Items => customOptions.values?.ToArray() ?? new KeyValuePair<int, IExtensionValue>[0];
311         }
312     }
313 }
314