| #region Copyright notice and license |
| // Protocol Buffers - Google's data interchange format |
| // Copyright 2017 Google Inc. All rights reserved. |
| // |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file or at |
| // https://developers.google.com/open-source/licenses/bsd |
| #endregion |
| |
| using Google.Protobuf.Collections; |
| using System; |
| using System.Collections; |
| using System.Collections.Generic; |
| using System.Diagnostics; |
| using System.Diagnostics.CodeAnalysis; |
| using System.Linq; |
| using System.Reflection; |
| |
| namespace Google.Protobuf.Reflection |
| { |
| /// <summary> |
| /// Container for a set of custom options specified within a message, field etc. |
| /// </summary> |
| /// <remarks> |
| /// <para> |
| /// This type is publicly immutable, but internally mutable. It is only populated |
| /// by the descriptor parsing code - by the time any user code is able to see an instance, |
| /// it will be fully initialized. |
| /// </para> |
| /// <para> |
| /// If an option is requested using the incorrect method, an answer may still be returned: all |
| /// of the numeric types are represented internally using 64-bit integers, for example. It is up to |
| /// the caller to ensure that they make the appropriate method call for the option they're interested in. |
| /// Note that enum options are simply stored as integers, so the value should be fetched using |
| /// <see cref="TryGetInt32(int, out int)"/> and then cast appropriately. |
| /// </para> |
| /// <para> |
| /// Repeated options are currently not supported. Asking for a single value of an option |
| /// which was actually repeated will return the last value, except for message types where |
| /// all the set values are merged together. |
| /// </para> |
| /// </remarks> |
| [DebuggerDisplay("Count = {DebugCount}")] |
| [DebuggerTypeProxy(typeof(CustomOptionsDebugView))] |
| public sealed class CustomOptions |
| { |
| private const string UnreferencedCodeMessage = "CustomOptions is incompatible with trimming."; |
| |
| private static readonly object[] EmptyParameters = new object[0]; |
| private readonly IDictionary<int, IExtensionValue> values; |
| |
| internal CustomOptions(IDictionary<int, IExtensionValue> values) |
| { |
| this.values = values; |
| } |
| |
| /// <summary> |
| /// Retrieves a Boolean value for the specified option field. |
| /// </summary> |
| /// <param name="field">The field to fetch the value for.</param> |
| /// <param name="value">The output variable to populate.</param> |
| /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns> |
| [RequiresUnreferencedCode(UnreferencedCodeMessage)] |
| public bool TryGetBool(int field, out bool value) => TryGetPrimitiveValue(field, out value); |
| |
| /// <summary> |
| /// Retrieves a signed 32-bit integer value for the specified option field. |
| /// </summary> |
| /// <param name="field">The field to fetch the value for.</param> |
| /// <param name="value">The output variable to populate.</param> |
| /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns> |
| [RequiresUnreferencedCode(UnreferencedCodeMessage)] |
| public bool TryGetInt32(int field, out int value) => TryGetPrimitiveValue(field, out value); |
| |
| /// <summary> |
| /// Retrieves a signed 64-bit integer value for the specified option field. |
| /// </summary> |
| /// <param name="field">The field to fetch the value for.</param> |
| /// <param name="value">The output variable to populate.</param> |
| /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns> |
| [RequiresUnreferencedCode(UnreferencedCodeMessage)] |
| public bool TryGetInt64(int field, out long value) => TryGetPrimitiveValue(field, out value); |
| |
| /// <summary> |
| /// Retrieves an unsigned 32-bit integer value for the specified option field, |
| /// assuming a fixed-length representation. |
| /// </summary> |
| /// <param name="field">The field to fetch the value for.</param> |
| /// <param name="value">The output variable to populate.</param> |
| /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns> |
| [RequiresUnreferencedCode(UnreferencedCodeMessage)] |
| public bool TryGetFixed32(int field, out uint value) => TryGetUInt32(field, out value); |
| |
| /// <summary> |
| /// Retrieves an unsigned 64-bit integer value for the specified option field, |
| /// assuming a fixed-length representation. |
| /// </summary> |
| /// <param name="field">The field to fetch the value for.</param> |
| /// <param name="value">The output variable to populate.</param> |
| /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns> |
| [RequiresUnreferencedCode(UnreferencedCodeMessage)] |
| public bool TryGetFixed64(int field, out ulong value) => TryGetUInt64(field, out value); |
| |
| /// <summary> |
| /// Retrieves a signed 32-bit integer value for the specified option field, |
| /// assuming a fixed-length representation. |
| /// </summary> |
| /// <param name="field">The field to fetch the value for.</param> |
| /// <param name="value">The output variable to populate.</param> |
| /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns> |
| [RequiresUnreferencedCode(UnreferencedCodeMessage)] |
| public bool TryGetSFixed32(int field, out int value) => TryGetInt32(field, out value); |
| |
| /// <summary> |
| /// Retrieves a signed 64-bit integer value for the specified option field, |
| /// assuming a fixed-length representation. |
| /// </summary> |
| /// <param name="field">The field to fetch the value for.</param> |
| /// <param name="value">The output variable to populate.</param> |
| /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns> |
| [RequiresUnreferencedCode(UnreferencedCodeMessage)] |
| public bool TryGetSFixed64(int field, out long value) => TryGetInt64(field, out value); |
| |
| /// <summary> |
| /// Retrieves a signed 32-bit integer value for the specified option field, |
| /// assuming a zigzag encoding. |
| /// </summary> |
| /// <param name="field">The field to fetch the value for.</param> |
| /// <param name="value">The output variable to populate.</param> |
| /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns> |
| [RequiresUnreferencedCode(UnreferencedCodeMessage)] |
| public bool TryGetSInt32(int field, out int value) => TryGetPrimitiveValue(field, out value); |
| |
| /// <summary> |
| /// Retrieves a signed 64-bit integer value for the specified option field, |
| /// assuming a zigzag encoding. |
| /// </summary> |
| /// <param name="field">The field to fetch the value for.</param> |
| /// <param name="value">The output variable to populate.</param> |
| /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns> |
| [RequiresUnreferencedCode(UnreferencedCodeMessage)] |
| public bool TryGetSInt64(int field, out long value) => TryGetPrimitiveValue(field, out value); |
| |
| /// <summary> |
| /// Retrieves an unsigned 32-bit integer value for the specified option field. |
| /// </summary> |
| /// <param name="field">The field to fetch the value for.</param> |
| /// <param name="value">The output variable to populate.</param> |
| /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns> |
| [RequiresUnreferencedCode(UnreferencedCodeMessage)] |
| public bool TryGetUInt32(int field, out uint value) => TryGetPrimitiveValue(field, out value); |
| |
| /// <summary> |
| /// Retrieves an unsigned 64-bit integer value for the specified option field. |
| /// </summary> |
| /// <param name="field">The field to fetch the value for.</param> |
| /// <param name="value">The output variable to populate.</param> |
| /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns> |
| [RequiresUnreferencedCode(UnreferencedCodeMessage)] |
| public bool TryGetUInt64(int field, out ulong value) => TryGetPrimitiveValue(field, out value); |
| |
| /// <summary> |
| /// Retrieves a 32-bit floating point value for the specified option field. |
| /// </summary> |
| /// <param name="field">The field to fetch the value for.</param> |
| /// <param name="value">The output variable to populate.</param> |
| /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns> |
| [RequiresUnreferencedCode(UnreferencedCodeMessage)] |
| public bool TryGetFloat(int field, out float value) => TryGetPrimitiveValue(field, out value); |
| |
| /// <summary> |
| /// Retrieves a 64-bit floating point value for the specified option field. |
| /// </summary> |
| /// <param name="field">The field to fetch the value for.</param> |
| /// <param name="value">The output variable to populate.</param> |
| /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns> |
| [RequiresUnreferencedCode(UnreferencedCodeMessage)] |
| public bool TryGetDouble(int field, out double value) => TryGetPrimitiveValue(field, out value); |
| |
| /// <summary> |
| /// Retrieves a string value for the specified option field. |
| /// </summary> |
| /// <param name="field">The field to fetch the value for.</param> |
| /// <param name="value">The output variable to populate.</param> |
| /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns> |
| [RequiresUnreferencedCode(UnreferencedCodeMessage)] |
| public bool TryGetString(int field, out string value) => TryGetPrimitiveValue(field, out value); |
| |
| /// <summary> |
| /// Retrieves a bytes value for the specified option field. |
| /// </summary> |
| /// <param name="field">The field to fetch the value for.</param> |
| /// <param name="value">The output variable to populate.</param> |
| /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns> |
| [RequiresUnreferencedCode(UnreferencedCodeMessage)] |
| public bool TryGetBytes(int field, out ByteString value) => TryGetPrimitiveValue(field, out value); |
| |
| /// <summary> |
| /// Retrieves a message value for the specified option field. |
| /// </summary> |
| /// <param name="field">The field to fetch the value for.</param> |
| /// <param name="value">The output variable to populate.</param> |
| /// <returns><c>true</c> if a suitable value for the field was found; <c>false</c> otherwise.</returns> |
| [RequiresUnreferencedCode(UnreferencedCodeMessage)] |
| public bool TryGetMessage<T>(int field, out T value) where T : class, IMessage, new() |
| { |
| if (values == null) |
| { |
| value = default; |
| return false; |
| } |
| |
| if (values.TryGetValue(field, out IExtensionValue extensionValue)) |
| { |
| if (extensionValue is ExtensionValue<T> single) |
| { |
| ByteString bytes = single.GetValue().ToByteString(); |
| value = new T(); |
| value.MergeFrom(bytes); |
| return true; |
| } |
| else if (extensionValue is RepeatedExtensionValue<T> repeated) |
| { |
| value = repeated.GetValue() |
| .Select(v => v.ToByteString()) |
| .Aggregate(new T(), (t, b) => |
| { |
| t.MergeFrom(b); |
| return t; |
| }); |
| return true; |
| } |
| } |
| |
| value = null; |
| return false; |
| } |
| |
| [RequiresUnreferencedCode(UnreferencedCodeMessage)] |
| private bool TryGetPrimitiveValue<T>(int field, out T value) |
| { |
| if (values == null) |
| { |
| value = default; |
| return false; |
| } |
| |
| if (values.TryGetValue(field, out IExtensionValue extensionValue)) |
| { |
| if (extensionValue is ExtensionValue<T> single) |
| { |
| value = single.GetValue(); |
| return true; |
| } |
| else if (extensionValue is RepeatedExtensionValue<T> repeated) |
| { |
| if (repeated.GetValue().Count != 0) |
| { |
| RepeatedField<T> repeatedField = repeated.GetValue(); |
| value = repeatedField[repeatedField.Count - 1]; |
| return true; |
| } |
| } |
| else // and here we find explicit enum handling since T : Enum ! x is ExtensionValue<Enum> |
| { |
| var type = extensionValue.GetType(); |
| if (type.GetGenericTypeDefinition() == typeof(ExtensionValue<>)) |
| { |
| var typeInfo = type.GetTypeInfo(); |
| var typeArgs = typeInfo.GenericTypeArguments; |
| if (typeArgs.Length == 1 && typeArgs[0].GetTypeInfo().IsEnum) |
| { |
| value = (T)typeInfo.GetDeclaredMethod(nameof(ExtensionValue<T>.GetValue)).Invoke(extensionValue, EmptyParameters); |
| return true; |
| } |
| } |
| else if (type.GetGenericTypeDefinition() == typeof(RepeatedExtensionValue<>)) |
| { |
| var typeInfo = type.GetTypeInfo(); |
| var typeArgs = typeInfo.GenericTypeArguments; |
| if (typeArgs.Length == 1 && typeArgs[0].GetTypeInfo().IsEnum) |
| { |
| var values = (IList)typeInfo.GetDeclaredMethod(nameof(RepeatedExtensionValue<T>.GetValue)).Invoke(extensionValue, EmptyParameters); |
| if (values.Count != 0) |
| { |
| value = (T)values[values.Count - 1]; |
| return true; |
| } |
| } |
| } |
| } |
| } |
| |
| value = default; |
| return false; |
| } |
| |
| private int DebugCount => values?.Count ?? 0; |
| |
| private sealed class CustomOptionsDebugView |
| { |
| private readonly CustomOptions customOptions; |
| |
| public CustomOptionsDebugView(CustomOptions customOptions) |
| { |
| this.customOptions = customOptions; |
| } |
| |
| [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] |
| public KeyValuePair<int, IExtensionValue>[] Items => customOptions.values?.ToArray() ?? new KeyValuePair<int, IExtensionValue>[0]; |
| } |
| } |
| } |