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