1 #region Copyright notice and license 2 // Protocol Buffers - Google's data interchange format 3 // Copyright 2015 Google Inc. All rights reserved. 4 // 5 // Use of this source code is governed by a BSD-style 6 // license that can be found in the LICENSE file or at 7 // https://developers.google.com/open-source/licenses/bsd 8 #endregion 9 10 using System.Collections; 11 using System.Collections.Generic; 12 using System.Diagnostics; 13 using Google.Protobuf.Reflection; 14 using Google.Protobuf.WellKnownTypes; 15 16 namespace Google.Protobuf 17 { 18 /// <summary> 19 /// <para>A tree representation of a FieldMask. Each leaf node in this tree represent 20 /// a field path in the FieldMask.</para> 21 /// 22 /// <para>For example, FieldMask "foo.bar,foo.baz,bar.baz" as a tree will be:</para> 23 /// <code> 24 /// [root] -+- foo -+- bar 25 /// | | 26 /// | +- baz 27 /// | 28 /// +- bar --- baz 29 /// </code> 30 /// 31 /// <para>By representing FieldMasks with this tree structure we can easily convert 32 /// a FieldMask to a canonical form, merge two FieldMasks, calculate the 33 /// intersection to two FieldMasks and traverse all fields specified by the 34 /// FieldMask in a message tree.</para> 35 /// </summary> 36 internal sealed class FieldMaskTree 37 { 38 private const char FIELD_PATH_SEPARATOR = '.'; 39 40 internal sealed class Node 41 { 42 public Dictionary<string, Node> Children { get; } = new Dictionary<string, Node>(); 43 } 44 45 private readonly Node root = new Node(); 46 47 /// <summary> 48 /// Creates an empty FieldMaskTree. 49 /// </summary> FieldMaskTree()50 public FieldMaskTree() 51 { 52 } 53 54 /// <summary> 55 /// Creates a FieldMaskTree for a given FieldMask. 56 /// </summary> FieldMaskTree(FieldMask mask)57 public FieldMaskTree(FieldMask mask) 58 { 59 MergeFromFieldMask(mask); 60 } 61 ToString()62 public override string ToString() 63 { 64 return ToFieldMask().ToString(); 65 } 66 67 /// <summary> 68 /// Adds a field path to the tree. In a FieldMask, every field path matches the 69 /// specified field as well as all its sub-fields. For example, a field path 70 /// "foo.bar" matches field "foo.bar" and also "foo.bar.baz", etc. When adding 71 /// a field path to the tree, redundant sub-paths will be removed. That is, 72 /// after adding "foo.bar" to the tree, "foo.bar.baz" will be removed if it 73 /// exists, which will turn the tree node for "foo.bar" to a leaf node. 74 /// Likewise, if the field path to add is a sub-path of an existing leaf node, 75 /// nothing will be changed in the tree. 76 /// </summary> AddFieldPath(string path)77 public FieldMaskTree AddFieldPath(string path) 78 { 79 var parts = path.Split(FIELD_PATH_SEPARATOR); 80 if (parts.Length == 0) 81 { 82 return this; 83 } 84 85 var node = root; 86 var createNewBranch = false; 87 88 // Find the matching node in the tree. 89 foreach (var part in parts) 90 { 91 // Check whether the path matches an existing leaf node. 92 if (!createNewBranch 93 && node != root 94 && node.Children.Count == 0) 95 { 96 // The path to add is a sub-path of an existing leaf node. 97 return this; 98 } 99 100 if (!node.Children.TryGetValue(part, out Node childNode)) 101 { 102 createNewBranch = true; 103 childNode = new Node(); 104 node.Children.Add(part, childNode); 105 } 106 node = childNode; 107 } 108 109 // Turn the matching node into a leaf node (i.e., remove sub-paths). 110 node.Children.Clear(); 111 return this; 112 } 113 114 /// <summary> 115 /// Merges all field paths in a FieldMask into this tree. 116 /// </summary> MergeFromFieldMask(FieldMask mask)117 public FieldMaskTree MergeFromFieldMask(FieldMask mask) 118 { 119 foreach (var path in mask.Paths) 120 { 121 AddFieldPath(path); 122 } 123 124 return this; 125 } 126 127 /// <summary> 128 /// Converts this tree to a FieldMask. 129 /// </summary> ToFieldMask()130 public FieldMask ToFieldMask() 131 { 132 var mask = new FieldMask(); 133 if (root.Children.Count != 0) 134 { 135 var paths = new List<string>(); 136 GetFieldPaths(root, "", paths); 137 mask.Paths.AddRange(paths); 138 } 139 140 return mask; 141 } 142 143 /// <summary> 144 /// Gathers all field paths in a sub-tree. 145 /// </summary> GetFieldPaths(Node node, string path, List<string> paths)146 private void GetFieldPaths(Node node, string path, List<string> paths) 147 { 148 if (node.Children.Count == 0) 149 { 150 paths.Add(path); 151 return; 152 } 153 154 foreach (var entry in node.Children) 155 { 156 var childPath = path.Length == 0 ? entry.Key : path + "." + entry.Key; 157 GetFieldPaths(entry.Value, childPath, paths); 158 } 159 } 160 161 /// <summary> 162 /// Adds the intersection of this tree with the given <paramref name="path"/> to <paramref name="output"/>. 163 /// </summary> IntersectFieldPath(string path, FieldMaskTree output)164 public void IntersectFieldPath(string path, FieldMaskTree output) 165 { 166 if (root.Children.Count == 0) 167 { 168 return; 169 } 170 171 var parts = path.Split(FIELD_PATH_SEPARATOR); 172 if (parts.Length == 0) 173 { 174 return; 175 } 176 177 var node = root; 178 foreach (var part in parts) 179 { 180 if (node != root 181 && node.Children.Count == 0) 182 { 183 // The given path is a sub-path of an existing leaf node in the tree. 184 output.AddFieldPath(path); 185 return; 186 } 187 188 if (!node.Children.TryGetValue(part, out node)) 189 { 190 return; 191 } 192 } 193 194 // We found a matching node for the path. All leaf children of this matching 195 // node is in the intersection. 196 var paths = new List<string>(); 197 GetFieldPaths(node, path, paths); 198 foreach (var value in paths) 199 { 200 output.AddFieldPath(value); 201 } 202 } 203 204 /// <summary> 205 /// Merges all fields specified by this FieldMaskTree from <paramref name="source"/> to <paramref name="destination"/>. 206 /// </summary> Merge(IMessage source, IMessage destination, FieldMask.MergeOptions options)207 public void Merge(IMessage source, IMessage destination, FieldMask.MergeOptions options) 208 { 209 if (source.Descriptor != destination.Descriptor) 210 { 211 throw new InvalidProtocolBufferException("Cannot merge messages of different types."); 212 } 213 214 if (root.Children.Count == 0) 215 { 216 return; 217 } 218 219 Merge(root, "", source, destination, options); 220 } 221 222 /// <summary> 223 /// Merges all fields specified by a sub-tree from <paramref name="source"/> to <paramref name="destination"/>. 224 /// </summary> Merge( Node node, string path, IMessage source, IMessage destination, FieldMask.MergeOptions options)225 private void Merge( 226 Node node, 227 string path, 228 IMessage source, 229 IMessage destination, 230 FieldMask.MergeOptions options) 231 { 232 if (source.Descriptor != destination.Descriptor) 233 { 234 throw new InvalidProtocolBufferException($"source ({source.Descriptor}) and destination ({destination.Descriptor}) descriptor must be equal"); 235 } 236 237 var descriptor = source.Descriptor; 238 foreach (var entry in node.Children) 239 { 240 var field = descriptor.FindFieldByName(entry.Key); 241 if (field == null) 242 { 243 Debug.WriteLine($"Cannot find field \"{entry.Key}\" in message type \"{descriptor.FullName}\""); 244 continue; 245 } 246 247 if (entry.Value.Children.Count != 0) 248 { 249 if (field.IsRepeated 250 || field.FieldType != FieldType.Message) 251 { 252 Debug.WriteLine($"Field \"{field.FullName}\" is not a singular message field and cannot have sub-fields."); 253 continue; 254 } 255 256 var sourceField = field.Accessor.GetValue(source); 257 var destinationField = field.Accessor.GetValue(destination); 258 if (sourceField == null 259 && destinationField == null) 260 { 261 // If the message field is not present in both source and destination, skip recursing 262 // so we don't create unnecessary empty messages. 263 continue; 264 } 265 266 if (destinationField == null) 267 { 268 // If we have to merge but the destination does not contain the field, create it. 269 destinationField = field.MessageType.Parser.CreateTemplate(); 270 field.Accessor.SetValue(destination, destinationField); 271 } 272 273 if (sourceField == null) 274 { 275 // If the message field is not present in the source but is in the destination, create an empty one 276 // so we can properly handle child entries 277 sourceField = field.MessageType.Parser.CreateTemplate(); 278 } 279 280 var childPath = path.Length == 0 ? entry.Key : path + "." + entry.Key; 281 Merge(entry.Value, childPath, (IMessage)sourceField, (IMessage)destinationField, options); 282 continue; 283 } 284 285 if (field.IsRepeated) 286 { 287 if (options.ReplaceRepeatedFields) 288 { 289 field.Accessor.Clear(destination); 290 } 291 292 var sourceField = (IList)field.Accessor.GetValue(source); 293 var destinationField = (IList)field.Accessor.GetValue(destination); 294 foreach (var element in sourceField) 295 { 296 destinationField.Add(element); 297 } 298 } 299 else 300 { 301 var sourceField = field.Accessor.GetValue(source); 302 if (field.FieldType == FieldType.Message) 303 { 304 if (options.ReplaceMessageFields) 305 { 306 if (sourceField == null) 307 { 308 field.Accessor.Clear(destination); 309 } 310 else 311 { 312 field.Accessor.SetValue(destination, sourceField); 313 } 314 } 315 else 316 { 317 if (sourceField != null) 318 { 319 // Well-known wrapper types are represented as nullable primitive types, so we do not "merge" them. 320 // Instead, any non-null value just overwrites the previous value directly. 321 if (field.MessageType.IsWrapperType) 322 { 323 field.Accessor.SetValue(destination, sourceField); 324 } 325 else 326 { 327 var sourceByteString = ((IMessage)sourceField).ToByteString(); 328 var destinationValue = (IMessage)field.Accessor.GetValue(destination); 329 if (destinationValue != null) 330 { 331 destinationValue.MergeFrom(sourceByteString); 332 } 333 else 334 { 335 field.Accessor.SetValue(destination, field.MessageType.Parser.ParseFrom(sourceByteString)); 336 } 337 } 338 } 339 } 340 } 341 else 342 { 343 if (sourceField != null 344 || !options.ReplacePrimitiveFields) 345 { 346 field.Accessor.SetValue(destination, sourceField); 347 } 348 else 349 { 350 field.Accessor.Clear(destination); 351 } 352 } 353 } 354 } 355 } 356 } 357 } 358