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