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