#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 System; using System.Collections.Generic; namespace 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 { /// /// Singleton for all descriptors with an empty set of options. /// internal static readonly CustomOptions Empty = new CustomOptions(); /// /// A sequence of values per field. This needs to be per field rather than per tag to allow correct deserialization /// of repeated fields which could be "int, ByteString, int" - unlikely as that is. The fact that values are boxed /// is unfortunate; we might be able to use a struct instead, and we could combine uint and ulong values. /// private readonly Dictionary> valuesByField = new Dictionary>(); private CustomOptions() { } /// /// 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) { ulong? tmp = GetLastNumericValue(field); value = tmp == 1UL; return tmp != null; } /// /// 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) { ulong? tmp = GetLastNumericValue(field); value = (int) tmp.GetValueOrDefault(); return tmp != null; } /// /// 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) { ulong? tmp = GetLastNumericValue(field); value = (long) tmp.GetValueOrDefault(); return tmp != null; } /// /// 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) { ulong? tmp = GetLastNumericValue(field); value = CodedInputStream.DecodeZigZag32((uint) tmp.GetValueOrDefault()); return tmp != null; } /// /// 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) { ulong? tmp = GetLastNumericValue(field); value = CodedInputStream.DecodeZigZag64(tmp.GetValueOrDefault()); return tmp != null; } /// /// 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) { ulong? tmp = GetLastNumericValue(field); value = (uint) tmp.GetValueOrDefault(); return tmp != null; } /// /// 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) { ulong? tmp = GetLastNumericValue(field); value = tmp.GetValueOrDefault(); return tmp != null; } /// /// 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) { ulong? tmp = GetLastNumericValue(field); int int32 = (int) tmp.GetValueOrDefault(); byte[] bytes = BitConverter.GetBytes(int32); value = BitConverter.ToSingle(bytes, 0); return tmp != null; } /// /// 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) { ulong? tmp = GetLastNumericValue(field); value = BitConverter.Int64BitsToDouble((long) tmp.GetValueOrDefault()); return tmp != null; } /// /// 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) { ByteString bytes = GetLastByteStringValue(field); value = bytes?.ToStringUtf8(); return bytes != null; } /// /// 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) { ByteString bytes = GetLastByteStringValue(field); value = bytes; return bytes != null; } /// /// 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() { value = null; List values; if (!valuesByField.TryGetValue(field, out values)) { return false; } foreach (FieldValue fieldValue in values) { if (fieldValue.ByteString != null) { if (value == null) { value = new T(); } value.MergeFrom(fieldValue.ByteString); } } return value != null; } private ulong? GetLastNumericValue(int field) { List values; if (!valuesByField.TryGetValue(field, out values)) { return null; } for (int i = values.Count - 1; i >= 0; i--) { // A non-bytestring value is a numeric value if (values[i].ByteString == null) { return values[i].Number; } } return null; } private ByteString GetLastByteStringValue(int field) { List values; if (!valuesByField.TryGetValue(field, out values)) { return null; } for (int i = values.Count - 1; i >= 0; i--) { if (values[i].ByteString != null) { return values[i].ByteString; } } return null; } /// /// Reads an unknown field, either parsing it and storing it or skipping it. /// /// /// If the current set of options is empty and we manage to read a field, a new set of options /// will be created and returned. Otherwise, the return value is this. This allows /// us to start with a singleton empty set of options and just create new ones where necessary. /// /// Input stream to read from. /// The resulting set of custom options, either this or a new set. internal CustomOptions ReadOrSkipUnknownField(CodedInputStream input) { var tag = input.LastTag; var field = WireFormat.GetTagFieldNumber(tag); switch (WireFormat.GetTagWireType(tag)) { case WireFormat.WireType.LengthDelimited: return AddValue(field, new FieldValue(input.ReadBytes())); case WireFormat.WireType.Fixed32: return AddValue(field, new FieldValue(input.ReadFixed32())); case WireFormat.WireType.Fixed64: return AddValue(field, new FieldValue(input.ReadFixed64())); case WireFormat.WireType.Varint: return AddValue(field, new FieldValue(input.ReadRawVarint64())); // For StartGroup, EndGroup or any wire format we don't understand, // just use the normal behavior (call SkipLastField). default: input.SkipLastField(); return this; } } private CustomOptions AddValue(int field, FieldValue value) { var ret = valuesByField.Count == 0 ? new CustomOptions() : this; List valuesForField; if (!ret.valuesByField.TryGetValue(field, out valuesForField)) { // Expect almost all valuesForField = new List(1); ret.valuesByField[field] = valuesForField; } valuesForField.Add(value); return ret; } /// /// All field values can be stored as a byte string or a 64-bit integer. /// This struct avoids unnecessary boxing. /// private struct FieldValue { internal ulong Number { get; } internal ByteString ByteString { get; } internal FieldValue(ulong number) { Number = number; ByteString = null; } internal FieldValue(ByteString byteString) { Number = 0; ByteString = byteString; } } } }