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