• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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