• 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 // 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