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