blob: f6fa1522ba6ca53a4560a96a821adcbbb5785a92 [file] [log] [blame]
Jon Skeet047575f2017-01-16 11:23:32 +00001#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
Sydney Acksman9e89b6e2019-05-03 15:54:41 -050033using Google.Protobuf.Collections;
Jon Skeet047575f2017-01-16 11:23:32 +000034using System;
Sydney Acksman9e89b6e2019-05-03 15:54:41 -050035using System.Collections;
Jon Skeet047575f2017-01-16 11:23:32 +000036using System.Collections.Generic;
James Newton-King22462b02021-11-03 10:50:09 +130037using System.Diagnostics.CodeAnalysis;
Sydney Acksman9e89b6e2019-05-03 15:54:41 -050038using System.Linq;
39using System.Reflection;
Jon Skeet047575f2017-01-16 11:23:32 +000040
41namespace 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 {
James Newton-King22462b02021-11-03 10:50:09 +130067 private const string UnreferencedCodeMessage = "CustomOptions is incompatible with trimming.";
68
Sydney Acksman9e89b6e2019-05-03 15:54:41 -050069 private static readonly object[] EmptyParameters = new object[0];
70 private readonly IDictionary<int, IExtensionValue> values;
Xiang Daie4794102019-02-21 11:28:50 +080071
Sydney Acksman9e89b6e2019-05-03 15:54:41 -050072 internal CustomOptions(IDictionary<int, IExtensionValue> values)
73 {
74 this.values = values;
75 }
Jon Skeet047575f2017-01-16 11:23:32 +000076
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>
James Newton-King22462b02021-11-03 10:50:09 +130083 [RequiresUnreferencedCode(UnreferencedCodeMessage)]
Sydney Acksman9e89b6e2019-05-03 15:54:41 -050084 public bool TryGetBool(int field, out bool value) => TryGetPrimitiveValue(field, out value);
Jon Skeet047575f2017-01-16 11:23:32 +000085
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>
James Newton-King22462b02021-11-03 10:50:09 +130092 [RequiresUnreferencedCode(UnreferencedCodeMessage)]
Sydney Acksman9e89b6e2019-05-03 15:54:41 -050093 public bool TryGetInt32(int field, out int value) => TryGetPrimitiveValue(field, out value);
Jon Skeet047575f2017-01-16 11:23:32 +000094
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>
James Newton-King22462b02021-11-03 10:50:09 +1300101 [RequiresUnreferencedCode(UnreferencedCodeMessage)]
Sydney Acksman9e89b6e2019-05-03 15:54:41 -0500102 public bool TryGetInt64(int field, out long value) => TryGetPrimitiveValue(field, out value);
Jon Skeet047575f2017-01-16 11:23:32 +0000103
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>
James Newton-King22462b02021-11-03 10:50:09 +1300111 [RequiresUnreferencedCode(UnreferencedCodeMessage)]
Jon Skeet047575f2017-01-16 11:23:32 +0000112 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>
James Newton-King22462b02021-11-03 10:50:09 +1300121 [RequiresUnreferencedCode(UnreferencedCodeMessage)]
Jon Skeet047575f2017-01-16 11:23:32 +0000122 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>
James Newton-King22462b02021-11-03 10:50:09 +1300131 [RequiresUnreferencedCode(UnreferencedCodeMessage)]
Jon Skeet047575f2017-01-16 11:23:32 +0000132 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>
James Newton-King22462b02021-11-03 10:50:09 +1300141 [RequiresUnreferencedCode(UnreferencedCodeMessage)]
Jon Skeet047575f2017-01-16 11:23:32 +0000142 public bool TryGetSFixed64(int field, out long value) => TryGetInt64(field, out value);
Xiang Daie4794102019-02-21 11:28:50 +0800143
Jon Skeet047575f2017-01-16 11:23:32 +0000144 /// <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>
James Newton-King22462b02021-11-03 10:50:09 +1300151 [RequiresUnreferencedCode(UnreferencedCodeMessage)]
Sydney Acksman9e89b6e2019-05-03 15:54:41 -0500152 public bool TryGetSInt32(int field, out int value) => TryGetPrimitiveValue(field, out value);
Jon Skeet047575f2017-01-16 11:23:32 +0000153
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>
James Newton-King22462b02021-11-03 10:50:09 +1300161 [RequiresUnreferencedCode(UnreferencedCodeMessage)]
Sydney Acksman9e89b6e2019-05-03 15:54:41 -0500162 public bool TryGetSInt64(int field, out long value) => TryGetPrimitiveValue(field, out value);
Jon Skeet047575f2017-01-16 11:23:32 +0000163
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>
James Newton-King22462b02021-11-03 10:50:09 +1300170 [RequiresUnreferencedCode(UnreferencedCodeMessage)]
Sydney Acksman9e89b6e2019-05-03 15:54:41 -0500171 public bool TryGetUInt32(int field, out uint value) => TryGetPrimitiveValue(field, out value);
Jon Skeet047575f2017-01-16 11:23:32 +0000172
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>
James Newton-King22462b02021-11-03 10:50:09 +1300179 [RequiresUnreferencedCode(UnreferencedCodeMessage)]
Sydney Acksman9e89b6e2019-05-03 15:54:41 -0500180 public bool TryGetUInt64(int field, out ulong value) => TryGetPrimitiveValue(field, out value);
Jon Skeet047575f2017-01-16 11:23:32 +0000181
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>
James Newton-King22462b02021-11-03 10:50:09 +1300188 [RequiresUnreferencedCode(UnreferencedCodeMessage)]
Sydney Acksman9e89b6e2019-05-03 15:54:41 -0500189 public bool TryGetFloat(int field, out float value) => TryGetPrimitiveValue(field, out value);
Jon Skeet047575f2017-01-16 11:23:32 +0000190
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>
James Newton-King22462b02021-11-03 10:50:09 +1300197 [RequiresUnreferencedCode(UnreferencedCodeMessage)]
Sydney Acksman9e89b6e2019-05-03 15:54:41 -0500198 public bool TryGetDouble(int field, out double value) => TryGetPrimitiveValue(field, out value);
Jon Skeet047575f2017-01-16 11:23:32 +0000199
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>
James Newton-King22462b02021-11-03 10:50:09 +1300206 [RequiresUnreferencedCode(UnreferencedCodeMessage)]
Sydney Acksman9e89b6e2019-05-03 15:54:41 -0500207 public bool TryGetString(int field, out string value) => TryGetPrimitiveValue(field, out value);
Jon Skeet047575f2017-01-16 11:23:32 +0000208
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>
James Newton-King22462b02021-11-03 10:50:09 +1300215 [RequiresUnreferencedCode(UnreferencedCodeMessage)]
Sydney Acksman9e89b6e2019-05-03 15:54:41 -0500216 public bool TryGetBytes(int field, out ByteString value) => TryGetPrimitiveValue(field, out value);
Jon Skeet047575f2017-01-16 11:23:32 +0000217
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>
James Newton-King22462b02021-11-03 10:50:09 +1300224 [RequiresUnreferencedCode(UnreferencedCodeMessage)]
Jon Skeet047575f2017-01-16 11:23:32 +0000225 public bool TryGetMessage<T>(int field, out T value) where T : class, IMessage, new()
226 {
Sydney Acksman9e89b6e2019-05-03 15:54:41 -0500227 if (values == null)
Jon Skeet047575f2017-01-16 11:23:32 +0000228 {
Sydney Acksman9e89b6e2019-05-03 15:54:41 -0500229 value = default(T);
Jon Skeet047575f2017-01-16 11:23:32 +0000230 return false;
231 }
Sydney Acksman9e89b6e2019-05-03 15:54:41 -0500232
233 IExtensionValue extensionValue;
234 if (values.TryGetValue(field, out extensionValue))
Jon Skeet047575f2017-01-16 11:23:32 +0000235 {
Sydney Acksman9e89b6e2019-05-03 15:54:41 -0500236 if (extensionValue is ExtensionValue<T>)
Jon Skeet047575f2017-01-16 11:23:32 +0000237 {
Sydney Acksman9e89b6e2019-05-03 15:54:41 -0500238 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
James Newton-King22462b02021-11-03 10:50:09 +1300262 [RequiresUnreferencedCode(UnreferencedCodeMessage)]
Sydney Acksman9e89b6e2019-05-03 15:54:41 -0500263 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>;
Sydney Acksmanf4cfd2d2019-05-05 14:51:13 -0500277 value = single.GetValue();
278 return true;
Jon Skeet047575f2017-01-16 11:23:32 +0000279 }
Sydney Acksman9e89b6e2019-05-03 15:54:41 -0500280 else if (extensionValue is RepeatedExtensionValue<T>)
Jon Skeet047575f2017-01-16 11:23:32 +0000281 {
Sydney Acksman9e89b6e2019-05-03 15:54:41 -0500282 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 }
Jon Skeet047575f2017-01-16 11:23:32 +0000289 }
Sydney Acksman9e89b6e2019-05-03 15:54:41 -0500290 else // and here we find explicit enum handling since T : Enum ! x is ExtensionValue<Enum>
Jon Skeet047575f2017-01-16 11:23:32 +0000291 {
Sydney Acksman9e89b6e2019-05-03 15:54:41 -0500292 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 {
Sydney Acksmanf4cfd2d2019-05-05 14:51:13 -0500299 value = (T)typeInfo.GetDeclaredMethod(nameof(ExtensionValue<T>.GetValue)).Invoke(extensionValue, EmptyParameters);
300 return true;
Sydney Acksman9e89b6e2019-05-03 15:54:41 -0500301 }
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 }
Jon Skeet047575f2017-01-16 11:23:32 +0000317 }
318 }
Jon Skeet047575f2017-01-16 11:23:32 +0000319
Sydney Acksman9e89b6e2019-05-03 15:54:41 -0500320 value = default(T);
321 return false;
Jon Skeet047575f2017-01-16 11:23:32 +0000322 }
323 }
324}