• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc.  All rights reserved.
3 // https://developers.google.com/protocol-buffers/
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
7 // met:
8 //
9 //     * Redistributions of source code must retain the above copyright
10 // notice, this list of conditions and the following disclaimer.
11 //     * Redistributions in binary form must reproduce the above
12 // copyright notice, this list of conditions and the following disclaimer
13 // in the documentation and/or other materials provided with the
14 // distribution.
15 //     * Neither the name of Google Inc. nor the names of its
16 // contributors may be used to endorse or promote products derived from
17 // this software without specific prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 
31 package com.google.protobuf.util;
32 
33 import static com.google.common.truth.Truth.assertThat;
34 
35 import com.google.protobuf.DynamicMessage;
36 import com.google.protobuf.Message;
37 import com.google.protobuf.UninitializedMessageException;
38 import protobuf_unittest.UnittestProto.NestedTestAllTypes;
39 import protobuf_unittest.UnittestProto.TestAllTypes;
40 import protobuf_unittest.UnittestProto.TestAllTypes.NestedMessage;
41 import protobuf_unittest.UnittestProto.TestRequired;
42 import protobuf_unittest.UnittestProto.TestRequiredMessage;
43 import junit.framework.TestCase;
44 
45 public class FieldMaskTreeTest extends TestCase {
testAddFieldPath()46   public void testAddFieldPath() throws Exception {
47     FieldMaskTree tree = new FieldMaskTree();
48     assertThat(tree.toString()).isEmpty();
49     tree.addFieldPath("");
50     assertThat(tree.toString()).isEmpty();
51     // New branch.
52     tree.addFieldPath("foo");
53     assertEquals("foo", tree.toString());
54     // Redundant path.
55     tree.addFieldPath("foo");
56     assertEquals("foo", tree.toString());
57     // New branch.
58     tree.addFieldPath("bar.baz");
59     assertEquals("bar.baz,foo", tree.toString());
60     // Redundant sub-path.
61     tree.addFieldPath("foo.bar");
62     assertEquals("bar.baz,foo", tree.toString());
63     // New branch from a non-root node.
64     tree.addFieldPath("bar.quz");
65     assertEquals("bar.baz,bar.quz,foo", tree.toString());
66     // A path that matches several existing sub-paths.
67     tree.addFieldPath("bar");
68     assertEquals("bar,foo", tree.toString());
69   }
70 
testMergeFromFieldMask()71   public void testMergeFromFieldMask() throws Exception {
72     FieldMaskTree tree = new FieldMaskTree(FieldMaskUtil.fromString("foo,bar.baz,bar.quz"));
73     assertEquals("bar.baz,bar.quz,foo", tree.toString());
74     tree.mergeFromFieldMask(FieldMaskUtil.fromString("foo.bar,bar"));
75     assertEquals("bar,foo", tree.toString());
76   }
77 
testRemoveFieldPath()78   public void testRemoveFieldPath() throws Exception {
79     String initialTreeString = "bar.baz,bar.quz.bar,foo";
80     FieldMaskTree tree;
81 
82     // Empty path.
83     tree = new FieldMaskTree(FieldMaskUtil.fromString(initialTreeString));
84     tree.removeFieldPath("");
85     assertEquals(initialTreeString, tree.toString());
86 
87     // Non-exist sub-path of an existing leaf.
88     tree = new FieldMaskTree(FieldMaskUtil.fromString(initialTreeString));
89     tree.removeFieldPath("foo.bar");
90     assertEquals(initialTreeString, tree.toString());
91 
92     // Non-exist path.
93     tree = new FieldMaskTree(FieldMaskUtil.fromString(initialTreeString));
94     tree.removeFieldPath("bar.foo");
95     assertEquals(initialTreeString, tree.toString());
96 
97     // Match an existing leaf node -> remove leaf node.
98     tree = new FieldMaskTree(FieldMaskUtil.fromString(initialTreeString));
99     tree.removeFieldPath("foo");
100     assertEquals("bar.baz,bar.quz.bar", tree.toString());
101 
102     // Match sub-path of an existing leaf node -> recursive removal.
103     tree = new FieldMaskTree(FieldMaskUtil.fromString(initialTreeString));
104     tree.removeFieldPath("bar.quz.bar");
105     assertEquals("bar.baz,foo", tree.toString());
106 
107     // Match a non-leaf node -> remove all children.
108     tree = new FieldMaskTree(FieldMaskUtil.fromString(initialTreeString));
109     tree.removeFieldPath("bar");
110     assertEquals("foo", tree.toString());
111   }
112 
testRemoveFromFieldMask()113   public void testRemoveFromFieldMask() throws Exception {
114     FieldMaskTree tree = new FieldMaskTree(FieldMaskUtil.fromString("foo,bar.baz,bar.quz"));
115     assertEquals("bar.baz,bar.quz,foo", tree.toString());
116     tree.removeFromFieldMask(FieldMaskUtil.fromString("foo.bar,bar"));
117     assertEquals("foo", tree.toString());
118   }
119 
testIntersectFieldPath()120   public void testIntersectFieldPath() throws Exception {
121     FieldMaskTree tree = new FieldMaskTree(FieldMaskUtil.fromString("foo,bar.baz,bar.quz"));
122     FieldMaskTree result = new FieldMaskTree();
123     // Empty path.
124     tree.intersectFieldPath("", result);
125     assertThat(result.toString()).isEmpty();
126     // Non-exist path.
127     tree.intersectFieldPath("quz", result);
128     assertThat(result.toString()).isEmpty();
129     // Sub-path of an existing leaf.
130     tree.intersectFieldPath("foo.bar", result);
131     assertEquals("foo.bar", result.toString());
132     // Match an existing leaf node.
133     tree.intersectFieldPath("foo", result);
134     assertEquals("foo", result.toString());
135     // Non-exist path.
136     tree.intersectFieldPath("bar.foo", result);
137     assertEquals("foo", result.toString());
138     // Match a non-leaf node.
139     tree.intersectFieldPath("bar", result);
140     assertEquals("bar.baz,bar.quz,foo", result.toString());
141   }
142 
testMerge()143   public void testMerge() throws Exception {
144     testMergeImpl(true);
145     testMergeImpl(false);
146     testMergeRequire(false);
147     testMergeRequire(true);
148   }
149 
merge( FieldMaskTree tree, Message source, Message.Builder builder, FieldMaskUtil.MergeOptions options, boolean useDynamicMessage)150   private void merge(
151       FieldMaskTree tree,
152       Message source,
153       Message.Builder builder,
154       FieldMaskUtil.MergeOptions options,
155       boolean useDynamicMessage)
156       throws Exception {
157     if (useDynamicMessage) {
158       Message.Builder newBuilder =
159           DynamicMessage.newBuilder(source.getDescriptorForType())
160               .mergeFrom(builder.buildPartial().toByteArray());
161       tree.merge(
162           DynamicMessage.newBuilder(source.getDescriptorForType())
163               .mergeFrom(source.toByteArray())
164               .build(),
165           newBuilder,
166           options);
167       builder.clear();
168       builder.mergeFrom(newBuilder.buildPartial());
169     } else {
170       tree.merge(source, builder, options);
171     }
172   }
173 
testMergeRequire(boolean useDynamicMessage)174   private void testMergeRequire(boolean useDynamicMessage) throws Exception {
175     TestRequired value = TestRequired.newBuilder().setA(4321).setB(8765).setC(233333).build();
176     TestRequiredMessage source = TestRequiredMessage.newBuilder().setRequiredMessage(value).build();
177 
178     FieldMaskUtil.MergeOptions options = new FieldMaskUtil.MergeOptions();
179     TestRequiredMessage.Builder builder = TestRequiredMessage.newBuilder();
180     merge(
181         new FieldMaskTree().addFieldPath("required_message.a"),
182         source,
183         builder,
184         options,
185         useDynamicMessage);
186     assertTrue(builder.hasRequiredMessage());
187     assertTrue(builder.getRequiredMessage().hasA());
188     assertFalse(builder.getRequiredMessage().hasB());
189     assertFalse(builder.getRequiredMessage().hasC());
190     merge(
191         new FieldMaskTree().addFieldPath("required_message.b").addFieldPath("required_message.c"),
192         source,
193         builder,
194         options,
195         useDynamicMessage);
196     try {
197       assertEquals(builder.build(), source);
198     } catch (UninitializedMessageException e) {
199       throw new AssertionError("required field isn't set", e);
200     }
201   }
202 
testMergeImpl(boolean useDynamicMessage)203   private void testMergeImpl(boolean useDynamicMessage) throws Exception {
204     TestAllTypes value =
205         TestAllTypes.newBuilder()
206             .setOptionalInt32(1234)
207             .setOptionalNestedMessage(NestedMessage.newBuilder().setBb(5678))
208             .addRepeatedInt32(4321)
209             .addRepeatedNestedMessage(NestedMessage.newBuilder().setBb(8765))
210             .build();
211     NestedTestAllTypes source =
212         NestedTestAllTypes.newBuilder()
213             .setPayload(value)
214             .setChild(NestedTestAllTypes.newBuilder().setPayload(value))
215             .build();
216     // Now we have a message source with the following structure:
217     //   [root] -+- payload -+- optional_int32
218     //           |           +- optional_nested_message
219     //           |           +- repeated_int32
220     //           |           +- repeated_nested_message
221     //           |
222     //           +- child --- payload -+- optional_int32
223     //                                 +- optional_nested_message
224     //                                 +- repeated_int32
225     //                                 +- repeated_nested_message
226 
227     FieldMaskUtil.MergeOptions options = new FieldMaskUtil.MergeOptions();
228 
229     // Test merging with an empty FieldMask.
230     NestedTestAllTypes.Builder builder = NestedTestAllTypes.newBuilder();
231     builder.getPayloadBuilder().addRepeatedInt32(1000);
232     merge(new FieldMaskTree(), source, builder, options, useDynamicMessage);
233     NestedTestAllTypes.Builder expected = NestedTestAllTypes.newBuilder();
234     expected.getPayloadBuilder().addRepeatedInt32(1000);
235     assertEquals(expected.build(), builder.build());
236 
237     // Test merging each individual field.
238     builder = NestedTestAllTypes.newBuilder();
239     merge(new FieldMaskTree().addFieldPath("payload.optional_int32"),
240         source, builder, options, useDynamicMessage);
241     expected = NestedTestAllTypes.newBuilder();
242     expected.getPayloadBuilder().setOptionalInt32(1234);
243     assertEquals(expected.build(), builder.build());
244 
245     builder = NestedTestAllTypes.newBuilder();
246     merge(new FieldMaskTree().addFieldPath("payload.optional_nested_message"),
247         source, builder, options, useDynamicMessage);
248     expected = NestedTestAllTypes.newBuilder();
249     expected.getPayloadBuilder().setOptionalNestedMessage(NestedMessage.newBuilder().setBb(5678));
250     assertEquals(expected.build(), builder.build());
251 
252     builder = NestedTestAllTypes.newBuilder();
253     merge(new FieldMaskTree().addFieldPath("payload.repeated_int32"),
254         source, builder, options, useDynamicMessage);
255     expected = NestedTestAllTypes.newBuilder();
256     expected.getPayloadBuilder().addRepeatedInt32(4321);
257     assertEquals(expected.build(), builder.build());
258 
259     builder = NestedTestAllTypes.newBuilder();
260     merge(new FieldMaskTree().addFieldPath("payload.repeated_nested_message"),
261         source, builder, options, useDynamicMessage);
262     expected = NestedTestAllTypes.newBuilder();
263     expected.getPayloadBuilder().addRepeatedNestedMessage(NestedMessage.newBuilder().setBb(8765));
264     assertEquals(expected.build(), builder.build());
265 
266     builder = NestedTestAllTypes.newBuilder();
267     merge(
268         new FieldMaskTree().addFieldPath("child.payload.optional_int32"),
269         source,
270         builder,
271         options,
272         useDynamicMessage);
273     expected = NestedTestAllTypes.newBuilder();
274     expected.getChildBuilder().getPayloadBuilder().setOptionalInt32(1234);
275     assertEquals(expected.build(), builder.build());
276 
277     builder = NestedTestAllTypes.newBuilder();
278     merge(
279         new FieldMaskTree().addFieldPath("child.payload.optional_nested_message"),
280         source,
281         builder,
282         options,
283         useDynamicMessage);
284     expected = NestedTestAllTypes.newBuilder();
285     expected
286         .getChildBuilder()
287         .getPayloadBuilder()
288         .setOptionalNestedMessage(NestedMessage.newBuilder().setBb(5678));
289     assertEquals(expected.build(), builder.build());
290 
291     builder = NestedTestAllTypes.newBuilder();
292     merge(new FieldMaskTree().addFieldPath("child.payload.repeated_int32"),
293         source, builder, options, useDynamicMessage);
294     expected = NestedTestAllTypes.newBuilder();
295     expected.getChildBuilder().getPayloadBuilder().addRepeatedInt32(4321);
296     assertEquals(expected.build(), builder.build());
297 
298     builder = NestedTestAllTypes.newBuilder();
299     merge(new FieldMaskTree().addFieldPath("child.payload.repeated_nested_message"),
300         source, builder, options, useDynamicMessage);
301     expected = NestedTestAllTypes.newBuilder();
302     expected
303         .getChildBuilder()
304         .getPayloadBuilder()
305         .addRepeatedNestedMessage(NestedMessage.newBuilder().setBb(8765));
306     assertEquals(expected.build(), builder.build());
307 
308     // Test merging all fields.
309     builder = NestedTestAllTypes.newBuilder();
310     merge(new FieldMaskTree().addFieldPath("child").addFieldPath("payload"),
311         source, builder, options, useDynamicMessage);
312     assertEquals(source, builder.build());
313 
314     // Test repeated options.
315     builder = NestedTestAllTypes.newBuilder();
316     builder.getPayloadBuilder().addRepeatedInt32(1000);
317     merge(new FieldMaskTree().addFieldPath("payload.repeated_int32"),
318         source, builder, options, useDynamicMessage);
319     // Default behavior is to append repeated fields.
320     assertEquals(2, builder.getPayload().getRepeatedInt32Count());
321     assertEquals(1000, builder.getPayload().getRepeatedInt32(0));
322     assertEquals(4321, builder.getPayload().getRepeatedInt32(1));
323     // Change to replace repeated fields.
324     options.setReplaceRepeatedFields(true);
325     merge(new FieldMaskTree().addFieldPath("payload.repeated_int32"),
326         source, builder, options, useDynamicMessage);
327     assertEquals(1, builder.getPayload().getRepeatedInt32Count());
328     assertEquals(4321, builder.getPayload().getRepeatedInt32(0));
329 
330     // Test message options.
331     builder = NestedTestAllTypes.newBuilder();
332     builder.getPayloadBuilder().setOptionalInt32(1000);
333     builder.getPayloadBuilder().setOptionalUint32(2000);
334     merge(new FieldMaskTree().addFieldPath("payload"),
335         source, builder, options, useDynamicMessage);
336     // Default behavior is to merge message fields.
337     assertEquals(1234, builder.getPayload().getOptionalInt32());
338     assertEquals(2000, builder.getPayload().getOptionalUint32());
339 
340     // Test merging unset message fields.
341     NestedTestAllTypes clearedSource = source.toBuilder().clearPayload().build();
342     builder = NestedTestAllTypes.newBuilder();
343     merge(new FieldMaskTree().addFieldPath("payload"),
344         clearedSource, builder, options, useDynamicMessage);
345     assertEquals(false, builder.hasPayload());
346 
347     // Skip a message field if they are unset in both source and target.
348     builder = NestedTestAllTypes.newBuilder();
349     merge(new FieldMaskTree().addFieldPath("payload.optional_int32"),
350         clearedSource, builder, options, useDynamicMessage);
351     assertEquals(false, builder.hasPayload());
352 
353     // Change to replace message fields.
354     options.setReplaceMessageFields(true);
355     builder = NestedTestAllTypes.newBuilder();
356     builder.getPayloadBuilder().setOptionalInt32(1000);
357     builder.getPayloadBuilder().setOptionalUint32(2000);
358     merge(new FieldMaskTree().addFieldPath("payload"),
359         source, builder, options, useDynamicMessage);
360     assertEquals(1234, builder.getPayload().getOptionalInt32());
361     assertEquals(0, builder.getPayload().getOptionalUint32());
362 
363     // Test merging unset message fields.
364     builder = NestedTestAllTypes.newBuilder();
365     builder.getPayloadBuilder().setOptionalInt32(1000);
366     builder.getPayloadBuilder().setOptionalUint32(2000);
367     merge(new FieldMaskTree().addFieldPath("payload"),
368         clearedSource, builder, options, useDynamicMessage);
369     assertEquals(false, builder.hasPayload());
370 
371     // Test merging unset primitive fields.
372     builder = source.toBuilder();
373     builder.getPayloadBuilder().clearOptionalInt32();
374     NestedTestAllTypes sourceWithPayloadInt32Unset = builder.build();
375     builder = source.toBuilder();
376     merge(new FieldMaskTree().addFieldPath("payload.optional_int32"),
377         sourceWithPayloadInt32Unset, builder, options, useDynamicMessage);
378     assertEquals(true, builder.getPayload().hasOptionalInt32());
379     assertEquals(0, builder.getPayload().getOptionalInt32());
380 
381     // Change to clear unset primitive fields.
382     options.setReplacePrimitiveFields(true);
383     builder = source.toBuilder();
384     merge(new FieldMaskTree().addFieldPath("payload.optional_int32"),
385         sourceWithPayloadInt32Unset, builder, options, useDynamicMessage);
386     assertEquals(true, builder.hasPayload());
387     assertEquals(false, builder.getPayload().hasOptionalInt32());
388   }
389 }
390