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