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 com.google.protobuf.DynamicMessage; 34 import com.google.protobuf.Message; 35 import com.google.protobuf.UninitializedMessageException; 36 import protobuf_unittest.UnittestProto.NestedTestAllTypes; 37 import protobuf_unittest.UnittestProto.TestAllTypes; 38 import protobuf_unittest.UnittestProto.TestAllTypes.NestedMessage; 39 import protobuf_unittest.UnittestProto.TestRequired; 40 import protobuf_unittest.UnittestProto.TestRequiredMessage; 41 import junit.framework.TestCase; 42 43 public class FieldMaskTreeTest extends TestCase { testAddFieldPath()44 public void testAddFieldPath() throws Exception { 45 FieldMaskTree tree = new FieldMaskTree(); 46 assertEquals("", tree.toString()); 47 tree.addFieldPath(""); 48 assertEquals("", tree.toString()); 49 // New branch. 50 tree.addFieldPath("foo"); 51 assertEquals("foo", tree.toString()); 52 // Redundant path. 53 tree.addFieldPath("foo"); 54 assertEquals("foo", tree.toString()); 55 // New branch. 56 tree.addFieldPath("bar.baz"); 57 assertEquals("bar.baz,foo", tree.toString()); 58 // Redundant sub-path. 59 tree.addFieldPath("foo.bar"); 60 assertEquals("bar.baz,foo", tree.toString()); 61 // New branch from a non-root node. 62 tree.addFieldPath("bar.quz"); 63 assertEquals("bar.baz,bar.quz,foo", tree.toString()); 64 // A path that matches several existing sub-paths. 65 tree.addFieldPath("bar"); 66 assertEquals("bar,foo", tree.toString()); 67 } 68 testMergeFromFieldMask()69 public void testMergeFromFieldMask() throws Exception { 70 FieldMaskTree tree = new FieldMaskTree(FieldMaskUtil.fromString("foo,bar.baz,bar.quz")); 71 assertEquals("bar.baz,bar.quz,foo", tree.toString()); 72 tree.mergeFromFieldMask(FieldMaskUtil.fromString("foo.bar,bar")); 73 assertEquals("bar,foo", tree.toString()); 74 } 75 testIntersectFieldPath()76 public void testIntersectFieldPath() throws Exception { 77 FieldMaskTree tree = new FieldMaskTree(FieldMaskUtil.fromString("foo,bar.baz,bar.quz")); 78 FieldMaskTree result = new FieldMaskTree(); 79 // Empty path. 80 tree.intersectFieldPath("", result); 81 assertEquals("", result.toString()); 82 // Non-exist path. 83 tree.intersectFieldPath("quz", result); 84 assertEquals("", result.toString()); 85 // Sub-path of an existing leaf. 86 tree.intersectFieldPath("foo.bar", result); 87 assertEquals("foo.bar", result.toString()); 88 // Match an existing leaf node. 89 tree.intersectFieldPath("foo", result); 90 assertEquals("foo", result.toString()); 91 // Non-exist path. 92 tree.intersectFieldPath("bar.foo", result); 93 assertEquals("foo", result.toString()); 94 // Match a non-leaf node. 95 tree.intersectFieldPath("bar", result); 96 assertEquals("bar.baz,bar.quz,foo", result.toString()); 97 } 98 testMerge()99 public void testMerge() throws Exception { 100 testMergeImpl(true); 101 testMergeImpl(false); 102 testMergeRequire(false); 103 testMergeRequire(true); 104 } 105 merge( FieldMaskTree tree, Message source, Message.Builder builder, FieldMaskUtil.MergeOptions options, boolean useDynamicMessage)106 private void merge( 107 FieldMaskTree tree, 108 Message source, 109 Message.Builder builder, 110 FieldMaskUtil.MergeOptions options, 111 boolean useDynamicMessage) 112 throws Exception { 113 if (useDynamicMessage) { 114 Message.Builder newBuilder = 115 DynamicMessage.newBuilder(source.getDescriptorForType()) 116 .mergeFrom(builder.buildPartial().toByteArray()); 117 tree.merge( 118 DynamicMessage.newBuilder(source.getDescriptorForType()) 119 .mergeFrom(source.toByteArray()) 120 .build(), 121 newBuilder, 122 options); 123 builder.clear(); 124 builder.mergeFrom(newBuilder.buildPartial()); 125 } else { 126 tree.merge(source, builder, options); 127 } 128 } 129 testMergeRequire(boolean useDynamicMessage)130 private void testMergeRequire(boolean useDynamicMessage) throws Exception { 131 TestRequired value = TestRequired.newBuilder().setA(4321).setB(8765).setC(233333).build(); 132 TestRequiredMessage source = TestRequiredMessage.newBuilder().setRequiredMessage(value).build(); 133 134 FieldMaskUtil.MergeOptions options = new FieldMaskUtil.MergeOptions(); 135 TestRequiredMessage.Builder builder = TestRequiredMessage.newBuilder(); 136 merge( 137 new FieldMaskTree().addFieldPath("required_message.a"), 138 source, 139 builder, 140 options, 141 useDynamicMessage); 142 assertTrue(builder.hasRequiredMessage()); 143 assertTrue(builder.getRequiredMessage().hasA()); 144 assertFalse(builder.getRequiredMessage().hasB()); 145 assertFalse(builder.getRequiredMessage().hasC()); 146 merge( 147 new FieldMaskTree().addFieldPath("required_message.b").addFieldPath("required_message.c"), 148 source, 149 builder, 150 options, 151 useDynamicMessage); 152 try { 153 assertEquals(builder.build(), source); 154 } catch (UninitializedMessageException e) { 155 throw new AssertionError("required field isn't set", e); 156 } 157 } 158 testMergeImpl(boolean useDynamicMessage)159 private void testMergeImpl(boolean useDynamicMessage) throws Exception { 160 TestAllTypes value = 161 TestAllTypes.newBuilder() 162 .setOptionalInt32(1234) 163 .setOptionalNestedMessage(NestedMessage.newBuilder().setBb(5678)) 164 .addRepeatedInt32(4321) 165 .addRepeatedNestedMessage(NestedMessage.newBuilder().setBb(8765)) 166 .build(); 167 NestedTestAllTypes source = 168 NestedTestAllTypes.newBuilder() 169 .setPayload(value) 170 .setChild(NestedTestAllTypes.newBuilder().setPayload(value)) 171 .build(); 172 // Now we have a message source with the following structure: 173 // [root] -+- payload -+- optional_int32 174 // | +- optional_nested_message 175 // | +- repeated_int32 176 // | +- repeated_nested_message 177 // | 178 // +- child --- payload -+- optional_int32 179 // +- optional_nested_message 180 // +- repeated_int32 181 // +- repeated_nested_message 182 183 FieldMaskUtil.MergeOptions options = new FieldMaskUtil.MergeOptions(); 184 185 // Test merging each individual field. 186 NestedTestAllTypes.Builder builder = NestedTestAllTypes.newBuilder(); 187 merge(new FieldMaskTree().addFieldPath("payload.optional_int32"), 188 source, builder, options, useDynamicMessage); 189 NestedTestAllTypes.Builder expected = NestedTestAllTypes.newBuilder(); 190 expected.getPayloadBuilder().setOptionalInt32(1234); 191 assertEquals(expected.build(), builder.build()); 192 193 builder = NestedTestAllTypes.newBuilder(); 194 merge(new FieldMaskTree().addFieldPath("payload.optional_nested_message"), 195 source, builder, options, useDynamicMessage); 196 expected = NestedTestAllTypes.newBuilder(); 197 expected.getPayloadBuilder().setOptionalNestedMessage(NestedMessage.newBuilder().setBb(5678)); 198 assertEquals(expected.build(), builder.build()); 199 200 builder = NestedTestAllTypes.newBuilder(); 201 merge(new FieldMaskTree().addFieldPath("payload.repeated_int32"), 202 source, builder, options, useDynamicMessage); 203 expected = NestedTestAllTypes.newBuilder(); 204 expected.getPayloadBuilder().addRepeatedInt32(4321); 205 assertEquals(expected.build(), builder.build()); 206 207 builder = NestedTestAllTypes.newBuilder(); 208 merge(new FieldMaskTree().addFieldPath("payload.repeated_nested_message"), 209 source, builder, options, useDynamicMessage); 210 expected = NestedTestAllTypes.newBuilder(); 211 expected.getPayloadBuilder().addRepeatedNestedMessage(NestedMessage.newBuilder().setBb(8765)); 212 assertEquals(expected.build(), builder.build()); 213 214 builder = NestedTestAllTypes.newBuilder(); 215 merge( 216 new FieldMaskTree().addFieldPath("child.payload.optional_int32"), 217 source, 218 builder, 219 options, 220 useDynamicMessage); 221 expected = NestedTestAllTypes.newBuilder(); 222 expected.getChildBuilder().getPayloadBuilder().setOptionalInt32(1234); 223 assertEquals(expected.build(), builder.build()); 224 225 builder = NestedTestAllTypes.newBuilder(); 226 merge( 227 new FieldMaskTree().addFieldPath("child.payload.optional_nested_message"), 228 source, 229 builder, 230 options, 231 useDynamicMessage); 232 expected = NestedTestAllTypes.newBuilder(); 233 expected 234 .getChildBuilder() 235 .getPayloadBuilder() 236 .setOptionalNestedMessage(NestedMessage.newBuilder().setBb(5678)); 237 assertEquals(expected.build(), builder.build()); 238 239 builder = NestedTestAllTypes.newBuilder(); 240 merge(new FieldMaskTree().addFieldPath("child.payload.repeated_int32"), 241 source, builder, options, useDynamicMessage); 242 expected = NestedTestAllTypes.newBuilder(); 243 expected.getChildBuilder().getPayloadBuilder().addRepeatedInt32(4321); 244 assertEquals(expected.build(), builder.build()); 245 246 builder = NestedTestAllTypes.newBuilder(); 247 merge(new FieldMaskTree().addFieldPath("child.payload.repeated_nested_message"), 248 source, builder, options, useDynamicMessage); 249 expected = NestedTestAllTypes.newBuilder(); 250 expected 251 .getChildBuilder() 252 .getPayloadBuilder() 253 .addRepeatedNestedMessage(NestedMessage.newBuilder().setBb(8765)); 254 assertEquals(expected.build(), builder.build()); 255 256 // Test merging all fields. 257 builder = NestedTestAllTypes.newBuilder(); 258 merge(new FieldMaskTree().addFieldPath("child").addFieldPath("payload"), 259 source, builder, options, useDynamicMessage); 260 assertEquals(source, builder.build()); 261 262 // Test repeated options. 263 builder = NestedTestAllTypes.newBuilder(); 264 builder.getPayloadBuilder().addRepeatedInt32(1000); 265 merge(new FieldMaskTree().addFieldPath("payload.repeated_int32"), 266 source, builder, options, useDynamicMessage); 267 // Default behavior is to append repeated fields. 268 assertEquals(2, builder.getPayload().getRepeatedInt32Count()); 269 assertEquals(1000, builder.getPayload().getRepeatedInt32(0)); 270 assertEquals(4321, builder.getPayload().getRepeatedInt32(1)); 271 // Change to replace repeated fields. 272 options.setReplaceRepeatedFields(true); 273 merge(new FieldMaskTree().addFieldPath("payload.repeated_int32"), 274 source, builder, options, useDynamicMessage); 275 assertEquals(1, builder.getPayload().getRepeatedInt32Count()); 276 assertEquals(4321, builder.getPayload().getRepeatedInt32(0)); 277 278 // Test message options. 279 builder = NestedTestAllTypes.newBuilder(); 280 builder.getPayloadBuilder().setOptionalInt32(1000); 281 builder.getPayloadBuilder().setOptionalUint32(2000); 282 merge(new FieldMaskTree().addFieldPath("payload"), 283 source, builder, options, useDynamicMessage); 284 // Default behavior is to merge message fields. 285 assertEquals(1234, builder.getPayload().getOptionalInt32()); 286 assertEquals(2000, builder.getPayload().getOptionalUint32()); 287 288 // Test merging unset message fields. 289 NestedTestAllTypes clearedSource = source.toBuilder().clearPayload().build(); 290 builder = NestedTestAllTypes.newBuilder(); 291 merge(new FieldMaskTree().addFieldPath("payload"), 292 clearedSource, builder, options, useDynamicMessage); 293 assertEquals(false, builder.hasPayload()); 294 295 // Skip a message field if they are unset in both source and target. 296 builder = NestedTestAllTypes.newBuilder(); 297 merge(new FieldMaskTree().addFieldPath("payload.optional_int32"), 298 clearedSource, builder, options, useDynamicMessage); 299 assertEquals(false, builder.hasPayload()); 300 301 // Change to replace message fields. 302 options.setReplaceMessageFields(true); 303 builder = NestedTestAllTypes.newBuilder(); 304 builder.getPayloadBuilder().setOptionalInt32(1000); 305 builder.getPayloadBuilder().setOptionalUint32(2000); 306 merge(new FieldMaskTree().addFieldPath("payload"), 307 source, builder, options, useDynamicMessage); 308 assertEquals(1234, builder.getPayload().getOptionalInt32()); 309 assertEquals(0, builder.getPayload().getOptionalUint32()); 310 311 // Test merging unset message fields. 312 builder = NestedTestAllTypes.newBuilder(); 313 builder.getPayloadBuilder().setOptionalInt32(1000); 314 builder.getPayloadBuilder().setOptionalUint32(2000); 315 merge(new FieldMaskTree().addFieldPath("payload"), 316 clearedSource, builder, options, useDynamicMessage); 317 assertEquals(false, builder.hasPayload()); 318 319 // Test merging unset primitive fields. 320 builder = source.toBuilder(); 321 builder.getPayloadBuilder().clearOptionalInt32(); 322 NestedTestAllTypes sourceWithPayloadInt32Unset = builder.build(); 323 builder = source.toBuilder(); 324 merge(new FieldMaskTree().addFieldPath("payload.optional_int32"), 325 sourceWithPayloadInt32Unset, builder, options, useDynamicMessage); 326 assertEquals(true, builder.getPayload().hasOptionalInt32()); 327 assertEquals(0, builder.getPayload().getOptionalInt32()); 328 329 // Change to clear unset primitive fields. 330 options.setReplacePrimitiveFields(true); 331 builder = source.toBuilder(); 332 merge(new FieldMaskTree().addFieldPath("payload.optional_int32"), 333 sourceWithPayloadInt32Unset, builder, options, useDynamicMessage); 334 assertEquals(true, builder.hasPayload()); 335 assertEquals(false, builder.getPayload().hasOptionalInt32()); 336 } 337 } 338