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