From 72f2e088b992d464ee7e782c952884df457891b2 Mon Sep 17 00:00:00 2001 From: ObsidianMinor Date: Wed, 8 Aug 2018 20:32:53 -0400 Subject: [PATCH] Library changes (initialized extension) --- .../Google.Protobuf/Collections/MapField.cs | 8 --- .../Collections/RepeatedField.cs | 22 ------ csharp/src/Google.Protobuf/IMessage.cs | 12 ---- .../src/Google.Protobuf/MessageExtensions.cs | 72 +++++++++++++++++++ csharp/src/Google.Protobuf/MessageParser.cs | 2 +- .../Reflection/FieldDescriptor.cs | 5 ++ .../Reflection/IFieldAccessor.cs | 2 +- .../Reflection/RepeatedFieldAccessor.cs | 2 +- 8 files changed, 80 insertions(+), 45 deletions(-) diff --git a/csharp/src/Google.Protobuf/Collections/MapField.cs b/csharp/src/Google.Protobuf/Collections/MapField.cs index fc7f2c3861ec..1924439e5cad 100644 --- a/csharp/src/Google.Protobuf/Collections/MapField.cs +++ b/csharp/src/Google.Protobuf/Collections/MapField.cs @@ -285,14 +285,6 @@ public void Clear() map.Clear(); } - /// - /// Determines whether all map message values are initialized - /// - public bool IsInitialized() - { - return list.All(kvp => kvp.Value is IMessage2 message ? message.IsInitialized() : true); - } - /// /// Determines whether map contains an entry equivalent to the given key/value pair. /// diff --git a/csharp/src/Google.Protobuf/Collections/RepeatedField.cs b/csharp/src/Google.Protobuf/Collections/RepeatedField.cs index efb1ca7ca54c..613ca9c48e7f 100644 --- a/csharp/src/Google.Protobuf/Collections/RepeatedField.cs +++ b/csharp/src/Google.Protobuf/Collections/RepeatedField.cs @@ -292,28 +292,6 @@ public bool Remove(T item) /// public bool IsReadOnly => false; - /// - /// Gets a value indicating whether the elements in the collection are all initialized - /// - public bool IsInitialized() - { - for (int i = 0; i < count; i++) - { - T value = array[i]; - if (value is IMessage2 message) - { - if (!message.IsInitialized()) - return false; - } - else - { - return true; - } - } - - return true; - } - /// /// Adds all of the specified values into this collection. /// diff --git a/csharp/src/Google.Protobuf/IMessage.cs b/csharp/src/Google.Protobuf/IMessage.cs index 8c88424c4f4d..d089f9463985 100644 --- a/csharp/src/Google.Protobuf/IMessage.cs +++ b/csharp/src/Google.Protobuf/IMessage.cs @@ -69,18 +69,6 @@ public interface IMessage MessageDescriptor Descriptor { get; } } - /// - /// Interface for a Protocol Buffers message, supporting - /// proto2 operations required for serialization - /// - public interface IMessage2 : IMessage - { - /// - /// Checks if all required fields have values set - /// - bool IsInitialized(); - } - /// /// Generic interface for a Protocol Buffers message, /// where the type parameter is expected to be the same type as diff --git a/csharp/src/Google.Protobuf/MessageExtensions.cs b/csharp/src/Google.Protobuf/MessageExtensions.cs index 62181eb9d9b3..b25b3feb95fc 100644 --- a/csharp/src/Google.Protobuf/MessageExtensions.cs +++ b/csharp/src/Google.Protobuf/MessageExtensions.cs @@ -30,7 +30,10 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #endregion +using Google.Protobuf.Reflection; +using System.Collections; using System.IO; +using System.Linq; namespace Google.Protobuf { @@ -140,6 +143,75 @@ public static ByteString ToByteString(this IMessage message) return ByteString.AttachBytes(message.ToByteArray()); } + /// + /// Checks if all required fields in a message have values set. For proto3 messages, this returns true + /// + public static bool IsInitialized(this IMessage message) + { + if (message.Descriptor.File.Proto.Syntax != "proto2") + { + return true; + } + + return message.Descriptor + .Fields + .InDeclarationOrder() + .Where(f => f.IsRequired || f.IsRepeated || f.IsMap || f.MessageType != null) + .All(f => + { + if (f.MessageType != null) + { + if (f.Accessor.HasValue(message)) + { + var field = (IMessage)f.Accessor.GetValue(message); + return field.IsInitialized(); + } + else + { + return false; + } + } + else if (f.IsRepeated) + { + IList list = (IList)f.Accessor.GetValue(message); + foreach (IMessage item in list) + { + if (item is null) + { + return true; + } + else if (!item.IsInitialized()) + { + return false; + } + } + + return true; + } + else if (f.IsMap) + { + IDictionary list = (IDictionary)f.Accessor.GetValue(message); + foreach (IMessage item in list.Values) + { + if (item is null) + { + return true; + } + else if (!item.IsInitialized()) + { + return false; + } + } + + return true; + } + else + { + return f.Accessor.HasValue(message); + } + }); + } + // Implementations allowing unknown fields to be discarded. internal static void MergeFrom(this IMessage message, byte[] data, bool discardUnknownFields) { diff --git a/csharp/src/Google.Protobuf/MessageParser.cs b/csharp/src/Google.Protobuf/MessageParser.cs index a65bc3bfa3e6..d3fa6cb1ee86 100644 --- a/csharp/src/Google.Protobuf/MessageParser.cs +++ b/csharp/src/Google.Protobuf/MessageParser.cs @@ -175,7 +175,7 @@ internal void MergeFrom(IMessage message, CodedInputStream codedInput) internal static void CheckMergedRequiredFields(IMessage message) { - if (message is IMessage2 proto2 && !proto2.IsInitialized()) + if (!message.IsInitialized()) throw new InvalidOperationException("Parsed message does not contain all required fields"); } diff --git a/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs index a18832aa2269..0a46f9eb02ab 100644 --- a/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs +++ b/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs @@ -183,6 +183,11 @@ private static FieldType GetFieldTypeFromProtoType(FieldDescriptorProto.Types.Ty /// public bool IsRepeated => Proto.Label == FieldDescriptorProto.Types.Label.Repeated; + /// + /// Returns true if this field is a required field; false otherwise. + /// + public bool IsRequired => Proto.Label == FieldDescriptorProto.Types.Label.Required; + /// /// Returns true if this field is a map field; false otherwise. /// diff --git a/csharp/src/Google.Protobuf/Reflection/IFieldAccessor.cs b/csharp/src/Google.Protobuf/Reflection/IFieldAccessor.cs index 400849fc1258..8f7ef3c67c92 100644 --- a/csharp/src/Google.Protobuf/Reflection/IFieldAccessor.cs +++ b/csharp/src/Google.Protobuf/Reflection/IFieldAccessor.cs @@ -52,7 +52,7 @@ public interface IFieldAccessor void Clear(IMessage message); /// - /// Indicates whether the field in the specified message is set. For proto3 fields and repeated fields, this throws an + /// Indicates whether the field in the specified message is set. For proto3 fields, this throws an /// bool HasValue(IMessage message); diff --git a/csharp/src/Google.Protobuf/Reflection/RepeatedFieldAccessor.cs b/csharp/src/Google.Protobuf/Reflection/RepeatedFieldAccessor.cs index 7f2eb977bf21..afb4a693fcef 100644 --- a/csharp/src/Google.Protobuf/Reflection/RepeatedFieldAccessor.cs +++ b/csharp/src/Google.Protobuf/Reflection/RepeatedFieldAccessor.cs @@ -53,7 +53,7 @@ public override void Clear(IMessage message) public override bool HasValue(IMessage message) { - throw new NotImplementedException(); + throw new InvalidOperationException("HasValue is not implemented for repeated fields"); } public override void SetValue(IMessage message, object value)