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