#region Copyright notice and license // Protocol Buffers - Google's data interchange format // Copyright 2017 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #endregion using LC.Google.Protobuf.Collections; using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace LC.Google.Protobuf.Reflection { /// /// Container for a set of custom options specified within a message, field etc. /// /// /// /// 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. /// /// /// 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 /// and then cast appropriately. /// /// /// 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. /// /// public sealed class CustomOptions { private static readonly object[] EmptyParameters = new object[0]; private readonly IDictionary values; internal CustomOptions(IDictionary values) { this.values = values; } /// /// Retrieves a Boolean value for the specified option field. /// /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. public bool TryGetBool(int field, out bool value) => TryGetPrimitiveValue(field, out value); /// /// Retrieves a signed 32-bit integer value for the specified option field. /// /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. public bool TryGetInt32(int field, out int value) => TryGetPrimitiveValue(field, out value); /// /// Retrieves a signed 64-bit integer value for the specified option field. /// /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. public bool TryGetInt64(int field, out long value) => TryGetPrimitiveValue(field, out value); /// /// Retrieves an unsigned 32-bit integer value for the specified option field, /// assuming a fixed-length representation. /// /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. public bool TryGetFixed32(int field, out uint value) => TryGetUInt32(field, out value); /// /// Retrieves an unsigned 64-bit integer value for the specified option field, /// assuming a fixed-length representation. /// /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. public bool TryGetFixed64(int field, out ulong value) => TryGetUInt64(field, out value); /// /// Retrieves a signed 32-bit integer value for the specified option field, /// assuming a fixed-length representation. /// /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. public bool TryGetSFixed32(int field, out int value) => TryGetInt32(field, out value); /// /// Retrieves a signed 64-bit integer value for the specified option field, /// assuming a fixed-length representation. /// /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. public bool TryGetSFixed64(int field, out long value) => TryGetInt64(field, out value); /// /// Retrieves a signed 32-bit integer value for the specified option field, /// assuming a zigzag encoding. /// /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. public bool TryGetSInt32(int field, out int value) => TryGetPrimitiveValue(field, out value); /// /// Retrieves a signed 64-bit integer value for the specified option field, /// assuming a zigzag encoding. /// /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. public bool TryGetSInt64(int field, out long value) => TryGetPrimitiveValue(field, out value); /// /// Retrieves an unsigned 32-bit integer value for the specified option field. /// /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. public bool TryGetUInt32(int field, out uint value) => TryGetPrimitiveValue(field, out value); /// /// Retrieves an unsigned 64-bit integer value for the specified option field. /// /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. public bool TryGetUInt64(int field, out ulong value) => TryGetPrimitiveValue(field, out value); /// /// Retrieves a 32-bit floating point value for the specified option field. /// /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. public bool TryGetFloat(int field, out float value) => TryGetPrimitiveValue(field, out value); /// /// Retrieves a 64-bit floating point value for the specified option field. /// /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. public bool TryGetDouble(int field, out double value) => TryGetPrimitiveValue(field, out value); /// /// Retrieves a string value for the specified option field. /// /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. public bool TryGetString(int field, out string value) => TryGetPrimitiveValue(field, out value); /// /// Retrieves a bytes value for the specified option field. /// /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. public bool TryGetBytes(int field, out ByteString value) => TryGetPrimitiveValue(field, out value); /// /// Retrieves a message value for the specified option field. /// /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. public bool TryGetMessage(int field, out T value) where T : class, IMessage, new() { if (values == null) { value = default(T); return false; } IExtensionValue extensionValue; if (values.TryGetValue(field, out extensionValue)) { if (extensionValue is ExtensionValue) { ExtensionValue single = extensionValue as ExtensionValue; ByteString bytes = single.GetValue().ToByteString(); value = new T(); value.MergeFrom(bytes); return true; } else if (extensionValue is RepeatedExtensionValue) { RepeatedExtensionValue repeated = extensionValue as RepeatedExtensionValue; value = repeated.GetValue() .Select(v => v.ToByteString()) .Aggregate(new T(), (t, b) => { t.MergeFrom(b); return t; }); return true; } } value = null; return false; } private bool TryGetPrimitiveValue(int field, out T value) { if (values == null) { value = default(T); return false; } IExtensionValue extensionValue; if (values.TryGetValue(field, out extensionValue)) { if (extensionValue is ExtensionValue) { ExtensionValue single = extensionValue as ExtensionValue; value = single.GetValue(); return true; } else if (extensionValue is RepeatedExtensionValue) { RepeatedExtensionValue repeated = extensionValue as RepeatedExtensionValue; if (repeated.GetValue().Count != 0) { RepeatedField repeatedField = repeated.GetValue(); value = repeatedField[repeatedField.Count - 1]; return true; } } else // and here we find explicit enum handling since T : Enum ! x is ExtensionValue { 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.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.GetValue)).Invoke(extensionValue, EmptyParameters); if (values.Count != 0) { value = (T)values[values.Count - 1]; return true; } } } } } value = default(T); return false; } } }