|  | #region Copyright notice and license | 
|  | // Protocol Buffers - Google's data interchange format | 
|  | // Copyright 2015 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 System.Collections; | 
|  | using System.Collections.Generic; | 
|  | using System.Diagnostics; | 
|  | using Google.Protobuf.Reflection; | 
|  | using Google.Protobuf.WellKnownTypes; | 
|  |  | 
|  | namespace Google.Protobuf | 
|  | { | 
|  | /// <summary> | 
|  | /// <para>A tree representation of a FieldMask. Each leaf node in this tree represent | 
|  | /// a field path in the FieldMask.</para> | 
|  | /// | 
|  | /// <para>For example, FieldMask "foo.bar,foo.baz,bar.baz" as a tree will be:</para> | 
|  | /// <code> | 
|  | ///   [root] -+- foo -+- bar | 
|  | ///           |       | | 
|  | ///           |       +- baz | 
|  | ///           | | 
|  | ///           +- bar --- baz | 
|  | /// </code> | 
|  | /// | 
|  | /// <para>By representing FieldMasks with this tree structure we can easily convert | 
|  | /// a FieldMask to a canonical form, merge two FieldMasks, calculate the | 
|  | /// intersection to two FieldMasks and traverse all fields specified by the | 
|  | /// FieldMask in a message tree.</para> | 
|  | /// </summary> | 
|  | internal sealed class FieldMaskTree | 
|  | { | 
|  | private const char FIELD_PATH_SEPARATOR = '.'; | 
|  |  | 
|  | internal sealed class Node | 
|  | { | 
|  | public Dictionary<string, Node> Children { get; } = new Dictionary<string, Node>(); | 
|  | } | 
|  |  | 
|  | private readonly Node root = new Node(); | 
|  |  | 
|  | /// <summary> | 
|  | /// Creates an empty FieldMaskTree. | 
|  | /// </summary> | 
|  | public FieldMaskTree() | 
|  | { | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// Creates a FieldMaskTree for a given FieldMask. | 
|  | /// </summary> | 
|  | public FieldMaskTree(FieldMask mask) | 
|  | { | 
|  | MergeFromFieldMask(mask); | 
|  | } | 
|  |  | 
|  | public override string ToString() | 
|  | { | 
|  | return ToFieldMask().ToString(); | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// Adds a field path to the tree. In a FieldMask, every field path matches the | 
|  | /// specified field as well as all its sub-fields. For example, a field path | 
|  | /// "foo.bar" matches field "foo.bar" and also "foo.bar.baz", etc. When adding | 
|  | /// a field path to the tree, redundant sub-paths will be removed. That is, | 
|  | /// after adding "foo.bar" to the tree, "foo.bar.baz" will be removed if it | 
|  | /// exists, which will turn the tree node for "foo.bar" to a leaf node. | 
|  | /// Likewise, if the field path to add is a sub-path of an existing leaf node, | 
|  | /// nothing will be changed in the tree. | 
|  | /// </summary> | 
|  | public FieldMaskTree AddFieldPath(string path) | 
|  | { | 
|  | var parts = path.Split(FIELD_PATH_SEPARATOR); | 
|  | if (parts.Length == 0) | 
|  | { | 
|  | return this; | 
|  | } | 
|  |  | 
|  | var node = root; | 
|  | var createNewBranch = false; | 
|  |  | 
|  | // Find the matching node in the tree. | 
|  | foreach (var part in parts) | 
|  | { | 
|  | // Check whether the path matches an existing leaf node. | 
|  | if (!createNewBranch | 
|  | && node != root | 
|  | && node.Children.Count == 0) | 
|  | { | 
|  | // The path to add is a sub-path of an existing leaf node. | 
|  | return this; | 
|  | } | 
|  |  | 
|  | if (!node.Children.TryGetValue(part, out Node childNode)) | 
|  | { | 
|  | createNewBranch = true; | 
|  | childNode = new Node(); | 
|  | node.Children.Add(part, childNode); | 
|  | } | 
|  | node = childNode; | 
|  | } | 
|  |  | 
|  | // Turn the matching node into a leaf node (i.e., remove sub-paths). | 
|  | node.Children.Clear(); | 
|  | return this; | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// Merges all field paths in a FieldMask into this tree. | 
|  | /// </summary> | 
|  | public FieldMaskTree MergeFromFieldMask(FieldMask mask) | 
|  | { | 
|  | foreach (var path in mask.Paths) | 
|  | { | 
|  | AddFieldPath(path); | 
|  | } | 
|  |  | 
|  | return this; | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// Converts this tree to a FieldMask. | 
|  | /// </summary> | 
|  | public FieldMask ToFieldMask() | 
|  | { | 
|  | var mask = new FieldMask(); | 
|  | if (root.Children.Count != 0) | 
|  | { | 
|  | var paths = new List<string>(); | 
|  | GetFieldPaths(root, "", paths); | 
|  | mask.Paths.AddRange(paths); | 
|  | } | 
|  |  | 
|  | return mask; | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// Gathers all field paths in a sub-tree. | 
|  | /// </summary> | 
|  | private void GetFieldPaths(Node node, string path, List<string> paths) | 
|  | { | 
|  | if (node.Children.Count == 0) | 
|  | { | 
|  | paths.Add(path); | 
|  | return; | 
|  | } | 
|  |  | 
|  | foreach (var entry in node.Children) | 
|  | { | 
|  | var childPath = path.Length == 0 ? entry.Key : path + "." + entry.Key; | 
|  | GetFieldPaths(entry.Value, childPath, paths); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// Adds the intersection of this tree with the given <paramref name="path"/> to <paramref name="output"/>. | 
|  | /// </summary> | 
|  | public void IntersectFieldPath(string path, FieldMaskTree output) | 
|  | { | 
|  | if (root.Children.Count == 0) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | var parts = path.Split(FIELD_PATH_SEPARATOR); | 
|  | if (parts.Length == 0) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | var node = root; | 
|  | foreach (var part in parts) | 
|  | { | 
|  | if (node != root | 
|  | && node.Children.Count == 0) | 
|  | { | 
|  | // The given path is a sub-path of an existing leaf node in the tree. | 
|  | output.AddFieldPath(path); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!node.Children.TryGetValue(part, out node)) | 
|  | { | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | // We found a matching node for the path. All leaf children of this matching | 
|  | // node is in the intersection. | 
|  | var paths = new List<string>(); | 
|  | GetFieldPaths(node, path, paths); | 
|  | foreach (var value in paths) | 
|  | { | 
|  | output.AddFieldPath(value); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// Merges all fields specified by this FieldMaskTree from <paramref name="source"/> to <paramref name="destination"/>. | 
|  | /// </summary> | 
|  | public void Merge(IMessage source, IMessage destination, FieldMask.MergeOptions options) | 
|  | { | 
|  | if (source.Descriptor != destination.Descriptor) | 
|  | { | 
|  | throw new InvalidProtocolBufferException("Cannot merge messages of different types."); | 
|  | } | 
|  |  | 
|  | if (root.Children.Count == 0) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | Merge(root, "", source, destination, options); | 
|  | } | 
|  |  | 
|  | /// <summary> | 
|  | /// Merges all fields specified by a sub-tree from <paramref name="source"/> to <paramref name="destination"/>. | 
|  | /// </summary> | 
|  | private void Merge( | 
|  | Node node, | 
|  | string path, | 
|  | IMessage source, | 
|  | IMessage destination, | 
|  | FieldMask.MergeOptions options) | 
|  | { | 
|  | if (source.Descriptor != destination.Descriptor) | 
|  | { | 
|  | throw new InvalidProtocolBufferException($"source ({source.Descriptor}) and destination ({destination.Descriptor}) descriptor must be equal"); | 
|  | } | 
|  |  | 
|  | var descriptor = source.Descriptor; | 
|  | foreach (var entry in node.Children) | 
|  | { | 
|  | var field = descriptor.FindFieldByName(entry.Key); | 
|  | if (field == null) | 
|  | { | 
|  | Debug.WriteLine($"Cannot find field \"{entry.Key}\" in message type \"{descriptor.FullName}\""); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (entry.Value.Children.Count != 0) | 
|  | { | 
|  | if (field.IsRepeated | 
|  | || field.FieldType != FieldType.Message) | 
|  | { | 
|  | Debug.WriteLine($"Field \"{field.FullName}\" is not a singular message field and cannot have sub-fields."); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | var sourceField = field.Accessor.GetValue(source); | 
|  | var destinationField = field.Accessor.GetValue(destination); | 
|  | if (sourceField == null | 
|  | && destinationField == null) | 
|  | { | 
|  | // If the message field is not present in both source and destination, skip recursing | 
|  | // so we don't create unnecessary empty messages. | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (destinationField == null) | 
|  | { | 
|  | // If we have to merge but the destination does not contain the field, create it. | 
|  | destinationField = field.MessageType.Parser.CreateTemplate(); | 
|  | field.Accessor.SetValue(destination, destinationField); | 
|  | } | 
|  |  | 
|  | if (sourceField == null) | 
|  | { | 
|  | // If the message field is not present in the source but is in the destination, create an empty one | 
|  | // so we can properly handle child entries | 
|  | sourceField = field.MessageType.Parser.CreateTemplate(); | 
|  | } | 
|  |  | 
|  | var childPath = path.Length == 0 ? entry.Key : path + "." + entry.Key; | 
|  | Merge(entry.Value, childPath, (IMessage)sourceField, (IMessage)destinationField, options); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (field.IsRepeated) | 
|  | { | 
|  | if (options.ReplaceRepeatedFields) | 
|  | { | 
|  | field.Accessor.Clear(destination); | 
|  | } | 
|  |  | 
|  | var sourceField = (IList)field.Accessor.GetValue(source); | 
|  | var destinationField = (IList)field.Accessor.GetValue(destination); | 
|  | foreach (var element in sourceField) | 
|  | { | 
|  | destinationField.Add(element); | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | var sourceField = field.Accessor.GetValue(source); | 
|  | if (field.FieldType == FieldType.Message) | 
|  | { | 
|  | if (options.ReplaceMessageFields) | 
|  | { | 
|  | if (sourceField == null) | 
|  | { | 
|  | field.Accessor.Clear(destination); | 
|  | } | 
|  | else | 
|  | { | 
|  | field.Accessor.SetValue(destination, sourceField); | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | if (sourceField != null) | 
|  | { | 
|  | // Well-known wrapper types are represented as nullable primitive types, so we do not "merge" them. | 
|  | // Instead, any non-null value just overwrites the previous value directly. | 
|  | if (field.MessageType.IsWrapperType) | 
|  | { | 
|  | field.Accessor.SetValue(destination, sourceField); | 
|  | } | 
|  | else | 
|  | { | 
|  | var sourceByteString = ((IMessage)sourceField).ToByteString(); | 
|  | var destinationValue = (IMessage)field.Accessor.GetValue(destination); | 
|  | if (destinationValue != null) | 
|  | { | 
|  | destinationValue.MergeFrom(sourceByteString); | 
|  | } | 
|  | else | 
|  | { | 
|  | field.Accessor.SetValue(destination, field.MessageType.Parser.ParseFrom(sourceByteString)); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | if (sourceField != null | 
|  | || !options.ReplacePrimitiveFields) | 
|  | { | 
|  | field.Accessor.SetValue(destination, sourceField); | 
|  | } | 
|  | else | 
|  | { | 
|  | field.Accessor.Clear(destination); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } |