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