• 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 = new FieldMaskTree(FieldMaskUtil.fromString(initialTreeString));
81     // Empty path.
82     tree.removeFieldPath("");
83     assertEquals(initialTreeString, tree.toString());
84     // Non-exist sub-path of an existing leaf.
85     tree.removeFieldPath("foo.bar");
86     assertEquals(initialTreeString, tree.toString());
87     // Non-exist path.
88     tree.removeFieldPath("bar.foo");
89     assertEquals(initialTreeString, tree.toString());
90     // Match an existing leaf node.
91     tree.removeFieldPath("foo");
92     assertEquals("bar.baz,bar.quz.bar", tree.toString());
93     // Match sub-path of an existing leaf node.
94     tree.removeFieldPath("bar.quz.bar");
95     assertEquals("bar.baz,bar.quz", tree.toString());
96     // Match a non-leaf node.
97     tree.removeFieldPath("bar");
98     assertThat(tree.toString()).isEmpty();
99   }
100 
testRemoveFromFieldMask()101   public void testRemoveFromFieldMask() throws Exception {
102     FieldMaskTree tree = new FieldMaskTree(FieldMaskUtil.fromString("foo,bar.baz,bar.quz"));
103     assertEquals("bar.baz,bar.quz,foo", tree.toString());
104     tree.removeFromFieldMask(FieldMaskUtil.fromString("foo.bar,bar"));
105     assertEquals("foo", tree.toString());
106   }
107 
testIntersectFieldPath()108   public void testIntersectFieldPath() throws Exception {
109     FieldMaskTree tree = new FieldMaskTree(FieldMaskUtil.fromString("foo,bar.baz,bar.quz"));
110     FieldMaskTree result = new FieldMaskTree();
111     // Empty path.
112     tree.intersectFieldPath("", result);
113     assertThat(result.toString()).isEmpty();
114     // Non-exist path.
115     tree.intersectFieldPath("quz", result);
116     assertThat(result.toString()).isEmpty();
117     // Sub-path of an existing leaf.
118     tree.intersectFieldPath("foo.bar", result);
119     assertEquals("foo.bar", result.toString());
120     // Match an existing leaf node.
121     tree.intersectFieldPath("foo", result);
122     assertEquals("foo", result.toString());
123     // Non-exist path.
124     tree.intersectFieldPath("bar.foo", result);
125     assertEquals("foo", result.toString());
126     // Match a non-leaf node.
127     tree.intersectFieldPath("bar", result);
128     assertEquals("bar.baz,bar.quz,foo", result.toString());
129   }
130 
testMerge()131   public void testMerge() throws Exception {
132     testMergeImpl(true);
133     testMergeImpl(false);
134     testMergeRequire(false);
135     testMergeRequire(true);
136   }
137 
merge( FieldMaskTree tree, Message source, Message.Builder builder, FieldMaskUtil.MergeOptions options, boolean useDynamicMessage)138   private void merge(
139       FieldMaskTree tree,
140       Message source,
141       Message.Builder builder,
142       FieldMaskUtil.MergeOptions options,
143       boolean useDynamicMessage)
144       throws Exception {
145     if (useDynamicMessage) {
146       Message.Builder newBuilder =
147           DynamicMessage.newBuilder(source.getDescriptorForType())
148               .mergeFrom(builder.buildPartial().toByteArray());
149       tree.merge(
150           DynamicMessage.newBuilder(source.getDescriptorForType())
151               .mergeFrom(source.toByteArray())
152               .build(),
153           newBuilder,
154           options);
155       builder.clear();
156       builder.mergeFrom(newBuilder.buildPartial());
157     } else {
158       tree.merge(source, builder, options);
159     }
160   }
161 
testMergeRequire(boolean useDynamicMessage)162   private void testMergeRequire(boolean useDynamicMessage) throws Exception {
163     TestRequired value = TestRequired.newBuilder().setA(4321).setB(8765).setC(233333).build();
164     TestRequiredMessage source = TestRequiredMessage.newBuilder().setRequiredMessage(value).build();
165 
166     FieldMaskUtil.MergeOptions options = new FieldMaskUtil.MergeOptions();
167     TestRequiredMessage.Builder builder = TestRequiredMessage.newBuilder();
168     merge(
169         new FieldMaskTree().addFieldPath("required_message.a"),
170         source,
171         builder,
172         options,
173         useDynamicMessage);
174     assertTrue(builder.hasRequiredMessage());
175     assertTrue(builder.getRequiredMessage().hasA());
176     assertFalse(builder.getRequiredMessage().hasB());
177     assertFalse(builder.getRequiredMessage().hasC());
178     merge(
179         new FieldMaskTree().addFieldPath("required_message.b").addFieldPath("required_message.c"),
180         source,
181         builder,
182         options,
183         useDynamicMessage);
184     try {
185       assertEquals(builder.build(), source);
186     } catch (UninitializedMessageException e) {
187       throw new AssertionError("required field isn't set", e);
188     }
189   }
190 
testMergeImpl(boolean useDynamicMessage)191   private void testMergeImpl(boolean useDynamicMessage) throws Exception {
192     TestAllTypes value =
193         TestAllTypes.newBuilder()
194             .setOptionalInt32(1234)
195             .setOptionalNestedMessage(NestedMessage.newBuilder().setBb(5678))
196             .addRepeatedInt32(4321)
197             .addRepeatedNestedMessage(NestedMessage.newBuilder().setBb(8765))
198             .build();
199     NestedTestAllTypes source =
200         NestedTestAllTypes.newBuilder()
201             .setPayload(value)
202             .setChild(NestedTestAllTypes.newBuilder().setPayload(value))
203             .build();
204     // Now we have a message source with the following structure:
205     //   [root] -+- payload -+- optional_int32
206     //           |           +- optional_nested_message
207     //           |           +- repeated_int32
208     //           |           +- repeated_nested_message
209     //           |
210     //           +- child --- payload -+- optional_int32
211     //                                 +- optional_nested_message
212     //                                 +- repeated_int32
213     //                                 +- repeated_nested_message
214 
215     FieldMaskUtil.MergeOptions options = new FieldMaskUtil.MergeOptions();
216 
217     // Test merging with an empty FieldMask.
218     NestedTestAllTypes.Builder builder = NestedTestAllTypes.newBuilder();
219     builder.getPayloadBuilder().addRepeatedInt32(1000);
220     merge(new FieldMaskTree(), source, builder, options, useDynamicMessage);
221     NestedTestAllTypes.Builder expected = NestedTestAllTypes.newBuilder();
222     expected.getPayloadBuilder().addRepeatedInt32(1000);
223     assertEquals(expected.build(), builder.build());
224 
225     // Test merging each individual field.
226     builder = NestedTestAllTypes.newBuilder();
227     merge(new FieldMaskTree().addFieldPath("payload.optional_int32"),
228         source, builder, options, useDynamicMessage);
229     expected = NestedTestAllTypes.newBuilder();
230     expected.getPayloadBuilder().setOptionalInt32(1234);
231     assertEquals(expected.build(), builder.build());
232 
233     builder = NestedTestAllTypes.newBuilder();
234     merge(new FieldMaskTree().addFieldPath("payload.optional_nested_message"),
235         source, builder, options, useDynamicMessage);
236     expected = NestedTestAllTypes.newBuilder();
237     expected.getPayloadBuilder().setOptionalNestedMessage(NestedMessage.newBuilder().setBb(5678));
238     assertEquals(expected.build(), builder.build());
239 
240     builder = NestedTestAllTypes.newBuilder();
241     merge(new FieldMaskTree().addFieldPath("payload.repeated_int32"),
242         source, builder, options, useDynamicMessage);
243     expected = NestedTestAllTypes.newBuilder();
244     expected.getPayloadBuilder().addRepeatedInt32(4321);
245     assertEquals(expected.build(), builder.build());
246 
247     builder = NestedTestAllTypes.newBuilder();
248     merge(new FieldMaskTree().addFieldPath("payload.repeated_nested_message"),
249         source, builder, options, useDynamicMessage);
250     expected = NestedTestAllTypes.newBuilder();
251     expected.getPayloadBuilder().addRepeatedNestedMessage(NestedMessage.newBuilder().setBb(8765));
252     assertEquals(expected.build(), builder.build());
253 
254     builder = NestedTestAllTypes.newBuilder();
255     merge(
256         new FieldMaskTree().addFieldPath("child.payload.optional_int32"),
257         source,
258         builder,
259         options,
260         useDynamicMessage);
261     expected = NestedTestAllTypes.newBuilder();
262     expected.getChildBuilder().getPayloadBuilder().setOptionalInt32(1234);
263     assertEquals(expected.build(), builder.build());
264 
265     builder = NestedTestAllTypes.newBuilder();
266     merge(
267         new FieldMaskTree().addFieldPath("child.payload.optional_nested_message"),
268         source,
269         builder,
270         options,
271         useDynamicMessage);
272     expected = NestedTestAllTypes.newBuilder();
273     expected
274         .getChildBuilder()
275         .getPayloadBuilder()
276         .setOptionalNestedMessage(NestedMessage.newBuilder().setBb(5678));
277     assertEquals(expected.build(), builder.build());
278 
279     builder = NestedTestAllTypes.newBuilder();
280     merge(new FieldMaskTree().addFieldPath("child.payload.repeated_int32"),
281         source, builder, options, useDynamicMessage);
282     expected = NestedTestAllTypes.newBuilder();
283     expected.getChildBuilder().getPayloadBuilder().addRepeatedInt32(4321);
284     assertEquals(expected.build(), builder.build());
285 
286     builder = NestedTestAllTypes.newBuilder();
287     merge(new FieldMaskTree().addFieldPath("child.payload.repeated_nested_message"),
288         source, builder, options, useDynamicMessage);
289     expected = NestedTestAllTypes.newBuilder();
290     expected
291         .getChildBuilder()
292         .getPayloadBuilder()
293         .addRepeatedNestedMessage(NestedMessage.newBuilder().setBb(8765));
294     assertEquals(expected.build(), builder.build());
295 
296     // Test merging all fields.
297     builder = NestedTestAllTypes.newBuilder();
298     merge(new FieldMaskTree().addFieldPath("child").addFieldPath("payload"),
299         source, builder, options, useDynamicMessage);
300     assertEquals(source, builder.build());
301 
302     // Test repeated options.
303     builder = NestedTestAllTypes.newBuilder();
304     builder.getPayloadBuilder().addRepeatedInt32(1000);
305     merge(new FieldMaskTree().addFieldPath("payload.repeated_int32"),
306         source, builder, options, useDynamicMessage);
307     // Default behavior is to append repeated fields.
308     assertEquals(2, builder.getPayload().getRepeatedInt32Count());
309     assertEquals(1000, builder.getPayload().getRepeatedInt32(0));
310     assertEquals(4321, builder.getPayload().getRepeatedInt32(1));
311     // Change to replace repeated fields.
312     options.setReplaceRepeatedFields(true);
313     merge(new FieldMaskTree().addFieldPath("payload.repeated_int32"),
314         source, builder, options, useDynamicMessage);
315     assertEquals(1, builder.getPayload().getRepeatedInt32Count());
316     assertEquals(4321, builder.getPayload().getRepeatedInt32(0));
317 
318     // Test message options.
319     builder = NestedTestAllTypes.newBuilder();
320     builder.getPayloadBuilder().setOptionalInt32(1000);
321     builder.getPayloadBuilder().setOptionalUint32(2000);
322     merge(new FieldMaskTree().addFieldPath("payload"),
323         source, builder, options, useDynamicMessage);
324     // Default behavior is to merge message fields.
325     assertEquals(1234, builder.getPayload().getOptionalInt32());
326     assertEquals(2000, builder.getPayload().getOptionalUint32());
327 
328     // Test merging unset message fields.
329     NestedTestAllTypes clearedSource = source.toBuilder().clearPayload().build();
330     builder = NestedTestAllTypes.newBuilder();
331     merge(new FieldMaskTree().addFieldPath("payload"),
332         clearedSource, builder, options, useDynamicMessage);
333     assertEquals(false, builder.hasPayload());
334 
335     // Skip a message field if they are unset in both source and target.
336     builder = NestedTestAllTypes.newBuilder();
337     merge(new FieldMaskTree().addFieldPath("payload.optional_int32"),
338         clearedSource, builder, options, useDynamicMessage);
339     assertEquals(false, builder.hasPayload());
340 
341     // Change to replace message fields.
342     options.setReplaceMessageFields(true);
343     builder = NestedTestAllTypes.newBuilder();
344     builder.getPayloadBuilder().setOptionalInt32(1000);
345     builder.getPayloadBuilder().setOptionalUint32(2000);
346     merge(new FieldMaskTree().addFieldPath("payload"),
347         source, builder, options, useDynamicMessage);
348     assertEquals(1234, builder.getPayload().getOptionalInt32());
349     assertEquals(0, builder.getPayload().getOptionalUint32());
350 
351     // Test merging unset message fields.
352     builder = NestedTestAllTypes.newBuilder();
353     builder.getPayloadBuilder().setOptionalInt32(1000);
354     builder.getPayloadBuilder().setOptionalUint32(2000);
355     merge(new FieldMaskTree().addFieldPath("payload"),
356         clearedSource, builder, options, useDynamicMessage);
357     assertEquals(false, builder.hasPayload());
358 
359     // Test merging unset primitive fields.
360     builder = source.toBuilder();
361     builder.getPayloadBuilder().clearOptionalInt32();
362     NestedTestAllTypes sourceWithPayloadInt32Unset = builder.build();
363     builder = source.toBuilder();
364     merge(new FieldMaskTree().addFieldPath("payload.optional_int32"),
365         sourceWithPayloadInt32Unset, builder, options, useDynamicMessage);
366     assertEquals(true, builder.getPayload().hasOptionalInt32());
367     assertEquals(0, builder.getPayload().getOptionalInt32());
368 
369     // Change to clear unset primitive fields.
370     options.setReplacePrimitiveFields(true);
371     builder = source.toBuilder();
372     merge(new FieldMaskTree().addFieldPath("payload.optional_int32"),
373         sourceWithPayloadInt32Unset, builder, options, useDynamicMessage);
374     assertEquals(true, builder.hasPayload());
375     assertEquals(false, builder.getPayload().hasOptionalInt32());
376   }
377 }
378