diff options
Diffstat (limited to 'java/util/src')
6 files changed, 189 insertions, 59 deletions
diff --git a/java/util/src/main/java/com/google/protobuf/util/Durations.java b/java/util/src/main/java/com/google/protobuf/util/Durations.java index fb7f4343..17b41cbb 100644 --- a/java/util/src/main/java/com/google/protobuf/util/Durations.java +++ b/java/util/src/main/java/com/google/protobuf/util/Durations.java @@ -61,6 +61,9 @@ public final class Durations { public static final Duration MAX_VALUE = Duration.newBuilder().setSeconds(DURATION_SECONDS_MAX).setNanos(999999999).build(); + /** A constant holding the duration of zero. */ + public static final Duration ZERO = Duration.newBuilder().setSeconds(0L).setNanos(0).build(); + private Durations() {} private static final Comparator<Duration> COMPARATOR = diff --git a/java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java b/java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java index 4a13fb1d..86f56ad9 100644 --- a/java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java +++ b/java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java @@ -240,7 +240,7 @@ final class FieldMaskTree { "Field \"" + field.getFullName() + "\" is not a " - + "singluar message field and cannot have sub-fields."); + + "singular message field and cannot have sub-fields."); continue; } if (!source.hasField(field) && !destination.hasField(field)) { @@ -249,12 +249,9 @@ final class FieldMaskTree { continue; } String childPath = path.isEmpty() ? entry.getKey() : path + "." + entry.getKey(); - merge( - entry.getValue(), - childPath, - (Message) source.getField(field), - destination.getFieldBuilder(field), - options); + Message.Builder childBuilder = ((Message) destination.getField(field)).toBuilder(); + merge(entry.getValue(), childPath, (Message) source.getField(field), childBuilder, options); + destination.setField(field, childBuilder.buildPartial()); continue; } if (field.isRepeated()) { @@ -275,7 +272,12 @@ final class FieldMaskTree { } } else { if (source.hasField(field)) { - destination.getFieldBuilder(field).mergeFrom((Message) source.getField(field)); + destination.setField( + field, + ((Message) destination.getField(field)) + .toBuilder() + .mergeFrom((Message) source.getField(field)) + .build()); } } } else { diff --git a/java/util/src/main/java/com/google/protobuf/util/FieldMaskUtil.java b/java/util/src/main/java/com/google/protobuf/util/FieldMaskUtil.java index b2f849c4..aedc5eac 100644 --- a/java/util/src/main/java/com/google/protobuf/util/FieldMaskUtil.java +++ b/java/util/src/main/java/com/google/protobuf/util/FieldMaskUtil.java @@ -235,7 +235,7 @@ public class FieldMaskUtil { /** * Converts a FieldMask to its canonical form. In the canonical form of a * FieldMask, all field paths are sorted alphabetically and redundant field - * paths are moved. + * paths are removed. */ public static FieldMask normalize(FieldMask mask) { return new FieldMaskTree(mask).toFieldMask(); diff --git a/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java b/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java index 7f69ee68..46deb5d1 100644 --- a/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java +++ b/java/util/src/main/java/com/google/protobuf/util/JsonFormat.java @@ -50,6 +50,7 @@ import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.EnumDescriptor; import com.google.protobuf.Descriptors.EnumValueDescriptor; import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.Descriptors.FieldDescriptor.Type; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Descriptors.OneofDescriptor; import com.google.protobuf.DoubleValue; @@ -610,7 +611,7 @@ public class JsonFormat { private final CharSequence blankOrNewLine; private static class GsonHolder { - private static final Gson DEFAULT_GSON = new GsonBuilder().disableHtmlEscaping().create(); + private static final Gson DEFAULT_GSON = new GsonBuilder().create(); } PrinterImpl( @@ -1539,7 +1540,11 @@ public class JsonFormat { Object key = parseFieldValue(keyField, new JsonPrimitive(entry.getKey()), entryBuilder); Object value = parseFieldValue(valueField, entry.getValue(), entryBuilder); if (value == null) { - throw new InvalidProtocolBufferException("Map value cannot be null."); + if (ignoringUnknownFields && valueField.getType() == Type.ENUM) { + continue; + } else { + throw new InvalidProtocolBufferException("Map value cannot be null."); + } } entryBuilder.setField(keyField, key); entryBuilder.setField(valueField, value); @@ -1557,8 +1562,12 @@ public class JsonFormat { for (int i = 0; i < array.size(); ++i) { Object value = parseFieldValue(field, array.get(i), builder); if (value == null) { - throw new InvalidProtocolBufferException( - "Repeated field elements cannot be null in field: " + field.getFullName()); + if (ignoringUnknownFields && field.getType() == Type.ENUM) { + continue; + } else { + throw new InvalidProtocolBufferException( + "Repeated field elements cannot be null in field: " + field.getFullName()); + } } builder.addRepeatedField(field, value); } @@ -1748,7 +1757,7 @@ public class JsonFormat { // an exception later. } - if (result == null) { + if (result == null && !ignoringUnknownFields) { throw new InvalidProtocolBufferException( "Invalid enum value: " + value + " for enum type: " + enumDescriptor.getFullName()); } diff --git a/java/util/src/test/java/com/google/protobuf/util/FieldMaskTreeTest.java b/java/util/src/test/java/com/google/protobuf/util/FieldMaskTreeTest.java index 853b6151..a0d317d4 100644 --- a/java/util/src/test/java/com/google/protobuf/util/FieldMaskTreeTest.java +++ b/java/util/src/test/java/com/google/protobuf/util/FieldMaskTreeTest.java @@ -30,9 +30,14 @@ package com.google.protobuf.util; +import com.google.protobuf.DynamicMessage; +import com.google.protobuf.Message; +import com.google.protobuf.UninitializedMessageException; import protobuf_unittest.UnittestProto.NestedTestAllTypes; import protobuf_unittest.UnittestProto.TestAllTypes; import protobuf_unittest.UnittestProto.TestAllTypes.NestedMessage; +import protobuf_unittest.UnittestProto.TestRequired; +import protobuf_unittest.UnittestProto.TestRequiredMessage; import junit.framework.TestCase; public class FieldMaskTreeTest extends TestCase { @@ -90,8 +95,68 @@ public class FieldMaskTreeTest extends TestCase { tree.intersectFieldPath("bar", result); assertEquals("bar.baz,bar.quz,foo", result.toString()); } - + public void testMerge() throws Exception { + testMergeImpl(true); + testMergeImpl(false); + testMergeRequire(false); + testMergeRequire(true); + } + + private void merge( + FieldMaskTree tree, + Message source, + Message.Builder builder, + FieldMaskUtil.MergeOptions options, + boolean useDynamicMessage) + throws Exception { + if (useDynamicMessage) { + Message.Builder newBuilder = + DynamicMessage.newBuilder(source.getDescriptorForType()) + .mergeFrom(builder.buildPartial().toByteArray()); + tree.merge( + DynamicMessage.newBuilder(source.getDescriptorForType()) + .mergeFrom(source.toByteArray()) + .build(), + newBuilder, + options); + builder.clear(); + builder.mergeFrom(newBuilder.buildPartial()); + } else { + tree.merge(source, builder, options); + } + } + + private void testMergeRequire(boolean useDynamicMessage) throws Exception { + TestRequired value = TestRequired.newBuilder().setA(4321).setB(8765).setC(233333).build(); + TestRequiredMessage source = TestRequiredMessage.newBuilder().setRequiredMessage(value).build(); + + FieldMaskUtil.MergeOptions options = new FieldMaskUtil.MergeOptions(); + TestRequiredMessage.Builder builder = TestRequiredMessage.newBuilder(); + merge( + new FieldMaskTree().addFieldPath("required_message.a"), + source, + builder, + options, + useDynamicMessage); + assertTrue(builder.hasRequiredMessage()); + assertTrue(builder.getRequiredMessage().hasA()); + assertFalse(builder.getRequiredMessage().hasB()); + assertFalse(builder.getRequiredMessage().hasC()); + merge( + new FieldMaskTree().addFieldPath("required_message.b").addFieldPath("required_message.c"), + source, + builder, + options, + useDynamicMessage); + try { + assertEquals(builder.build(), source); + } catch (UninitializedMessageException e) { + throw new AssertionError("required field isn't set", e); + } + } + + private void testMergeImpl(boolean useDynamicMessage) throws Exception { TestAllTypes value = TestAllTypes.newBuilder() .setOptionalInt32(1234) @@ -119,45 +184,51 @@ public class FieldMaskTreeTest extends TestCase { // Test merging each individual field. NestedTestAllTypes.Builder builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree().addFieldPath("payload.optional_int32").merge(source, builder, options); + merge(new FieldMaskTree().addFieldPath("payload.optional_int32"), + source, builder, options, useDynamicMessage); NestedTestAllTypes.Builder expected = NestedTestAllTypes.newBuilder(); expected.getPayloadBuilder().setOptionalInt32(1234); assertEquals(expected.build(), builder.build()); builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree() - .addFieldPath("payload.optional_nested_message") - .merge(source, builder, options); + merge(new FieldMaskTree().addFieldPath("payload.optional_nested_message"), + source, builder, options, useDynamicMessage); expected = NestedTestAllTypes.newBuilder(); expected.getPayloadBuilder().setOptionalNestedMessage(NestedMessage.newBuilder().setBb(5678)); assertEquals(expected.build(), builder.build()); builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree().addFieldPath("payload.repeated_int32").merge(source, builder, options); + merge(new FieldMaskTree().addFieldPath("payload.repeated_int32"), + source, builder, options, useDynamicMessage); expected = NestedTestAllTypes.newBuilder(); expected.getPayloadBuilder().addRepeatedInt32(4321); assertEquals(expected.build(), builder.build()); builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree() - .addFieldPath("payload.repeated_nested_message") - .merge(source, builder, options); + merge(new FieldMaskTree().addFieldPath("payload.repeated_nested_message"), + source, builder, options, useDynamicMessage); expected = NestedTestAllTypes.newBuilder(); expected.getPayloadBuilder().addRepeatedNestedMessage(NestedMessage.newBuilder().setBb(8765)); assertEquals(expected.build(), builder.build()); builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree() - .addFieldPath("child.payload.optional_int32") - .merge(source, builder, options); + merge( + new FieldMaskTree().addFieldPath("child.payload.optional_int32"), + source, + builder, + options, + useDynamicMessage); expected = NestedTestAllTypes.newBuilder(); expected.getChildBuilder().getPayloadBuilder().setOptionalInt32(1234); assertEquals(expected.build(), builder.build()); builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree() - .addFieldPath("child.payload.optional_nested_message") - .merge(source, builder, options); + merge( + new FieldMaskTree().addFieldPath("child.payload.optional_nested_message"), + source, + builder, + options, + useDynamicMessage); expected = NestedTestAllTypes.newBuilder(); expected .getChildBuilder() @@ -166,17 +237,15 @@ public class FieldMaskTreeTest extends TestCase { assertEquals(expected.build(), builder.build()); builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree() - .addFieldPath("child.payload.repeated_int32") - .merge(source, builder, options); + merge(new FieldMaskTree().addFieldPath("child.payload.repeated_int32"), + source, builder, options, useDynamicMessage); expected = NestedTestAllTypes.newBuilder(); expected.getChildBuilder().getPayloadBuilder().addRepeatedInt32(4321); assertEquals(expected.build(), builder.build()); builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree() - .addFieldPath("child.payload.repeated_nested_message") - .merge(source, builder, options); + merge(new FieldMaskTree().addFieldPath("child.payload.repeated_nested_message"), + source, builder, options, useDynamicMessage); expected = NestedTestAllTypes.newBuilder(); expected .getChildBuilder() @@ -186,23 +255,23 @@ public class FieldMaskTreeTest extends TestCase { // Test merging all fields. builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree() - .addFieldPath("child") - .addFieldPath("payload") - .merge(source, builder, options); + merge(new FieldMaskTree().addFieldPath("child").addFieldPath("payload"), + source, builder, options, useDynamicMessage); assertEquals(source, builder.build()); // Test repeated options. builder = NestedTestAllTypes.newBuilder(); builder.getPayloadBuilder().addRepeatedInt32(1000); - new FieldMaskTree().addFieldPath("payload.repeated_int32").merge(source, builder, options); + merge(new FieldMaskTree().addFieldPath("payload.repeated_int32"), + source, builder, options, useDynamicMessage); // Default behavior is to append repeated fields. assertEquals(2, builder.getPayload().getRepeatedInt32Count()); assertEquals(1000, builder.getPayload().getRepeatedInt32(0)); assertEquals(4321, builder.getPayload().getRepeatedInt32(1)); // Change to replace repeated fields. options.setReplaceRepeatedFields(true); - new FieldMaskTree().addFieldPath("payload.repeated_int32").merge(source, builder, options); + merge(new FieldMaskTree().addFieldPath("payload.repeated_int32"), + source, builder, options, useDynamicMessage); assertEquals(1, builder.getPayload().getRepeatedInt32Count()); assertEquals(4321, builder.getPayload().getRepeatedInt32(0)); @@ -210,7 +279,8 @@ public class FieldMaskTreeTest extends TestCase { builder = NestedTestAllTypes.newBuilder(); builder.getPayloadBuilder().setOptionalInt32(1000); builder.getPayloadBuilder().setOptionalUint32(2000); - new FieldMaskTree().addFieldPath("payload").merge(source, builder, options); + merge(new FieldMaskTree().addFieldPath("payload"), + source, builder, options, useDynamicMessage); // Default behavior is to merge message fields. assertEquals(1234, builder.getPayload().getOptionalInt32()); assertEquals(2000, builder.getPayload().getOptionalUint32()); @@ -218,14 +288,14 @@ public class FieldMaskTreeTest extends TestCase { // Test merging unset message fields. NestedTestAllTypes clearedSource = source.toBuilder().clearPayload().build(); builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree().addFieldPath("payload").merge(clearedSource, builder, options); + merge(new FieldMaskTree().addFieldPath("payload"), + clearedSource, builder, options, useDynamicMessage); assertEquals(false, builder.hasPayload()); // Skip a message field if they are unset in both source and target. builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree() - .addFieldPath("payload.optional_int32") - .merge(clearedSource, builder, options); + merge(new FieldMaskTree().addFieldPath("payload.optional_int32"), + clearedSource, builder, options, useDynamicMessage); assertEquals(false, builder.hasPayload()); // Change to replace message fields. @@ -233,7 +303,8 @@ public class FieldMaskTreeTest extends TestCase { builder = NestedTestAllTypes.newBuilder(); builder.getPayloadBuilder().setOptionalInt32(1000); builder.getPayloadBuilder().setOptionalUint32(2000); - new FieldMaskTree().addFieldPath("payload").merge(source, builder, options); + merge(new FieldMaskTree().addFieldPath("payload"), + source, builder, options, useDynamicMessage); assertEquals(1234, builder.getPayload().getOptionalInt32()); assertEquals(0, builder.getPayload().getOptionalUint32()); @@ -241,7 +312,8 @@ public class FieldMaskTreeTest extends TestCase { builder = NestedTestAllTypes.newBuilder(); builder.getPayloadBuilder().setOptionalInt32(1000); builder.getPayloadBuilder().setOptionalUint32(2000); - new FieldMaskTree().addFieldPath("payload").merge(clearedSource, builder, options); + merge(new FieldMaskTree().addFieldPath("payload"), + clearedSource, builder, options, useDynamicMessage); assertEquals(false, builder.hasPayload()); // Test merging unset primitive fields. @@ -249,18 +321,16 @@ public class FieldMaskTreeTest extends TestCase { builder.getPayloadBuilder().clearOptionalInt32(); NestedTestAllTypes sourceWithPayloadInt32Unset = builder.build(); builder = source.toBuilder(); - new FieldMaskTree() - .addFieldPath("payload.optional_int32") - .merge(sourceWithPayloadInt32Unset, builder, options); + merge(new FieldMaskTree().addFieldPath("payload.optional_int32"), + sourceWithPayloadInt32Unset, builder, options, useDynamicMessage); assertEquals(true, builder.getPayload().hasOptionalInt32()); assertEquals(0, builder.getPayload().getOptionalInt32()); // Change to clear unset primitive fields. options.setReplacePrimitiveFields(true); builder = source.toBuilder(); - new FieldMaskTree() - .addFieldPath("payload.optional_int32") - .merge(sourceWithPayloadInt32Unset, builder, options); + merge(new FieldMaskTree().addFieldPath("payload.optional_int32"), + sourceWithPayloadInt32Unset, builder, options, useDynamicMessage); assertEquals(true, builder.hasPayload()); assertEquals(false, builder.getPayload().hasOptionalInt32()); } diff --git a/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java b/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java index 6ef08508..64413bc1 100644 --- a/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java +++ b/java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java @@ -70,7 +70,6 @@ import java.io.StringReader; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.Set; @@ -162,6 +161,11 @@ public class JsonFormatTest extends TestCase { JsonFormat.parser().merge(json, builder); } + private void mergeFromJsonIgnoringUnknownFields(String json, Message.Builder builder) + throws IOException { + JsonFormat.parser().ignoringUnknownFields().merge(json, builder); + } + public void testAllFields() throws Exception { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); setAllFields(builder); @@ -669,11 +673,20 @@ public class JsonFormatTest extends TestCase { + "}", builder); fail(); + } catch (InvalidProtocolBufferException e) { // Exception expected. } } + public void testMapEnumNullValueIsIgnored() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + mergeFromJsonIgnoringUnknownFields( + "{\n" + " \"int32ToEnumMap\": {\"1\": null}\n" + "}", builder); + TestMap map = builder.build(); + assertEquals(0, map.getInt32ToEnumMapMap().size()); + } + public void testParserAcceptNonQuotedObjectKey() throws Exception { TestMap.Builder builder = TestMap.newBuilder(); mergeFromJson( @@ -1174,6 +1187,35 @@ public class JsonFormatTest extends TestCase { JsonFormat.parser().ignoringUnknownFields().merge(json, builder); } + public void testParserIgnoringUnknownEnums() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + String json = "{\n" + " \"optionalNestedEnum\": \"XXX\"\n" + "}"; + JsonFormat.parser().ignoringUnknownFields().merge(json, builder); + assertEquals(0, builder.getOptionalNestedEnumValue()); + } + + public void testUnknownEnumMap() throws Exception { + TestMap.Builder builder = TestMap.newBuilder(); + JsonFormat.parser() + .ignoringUnknownFields() + .merge("{\n" + " \"int32ToEnumMap\": {1: XXX, 2: FOO}" + "}", builder); + + assertEquals(NestedEnum.FOO, builder.getInt32ToEnumMapMap().get(2)); + assertEquals(1, builder.getInt32ToEnumMapMap().size()); + } + + public void testRepeatedUnknownEnum() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + JsonFormat.parser() + .ignoringUnknownFields() + .merge("{\n" + " \"repeatedNestedEnum\": [XXX, FOO, BAR, BAZ]" + "}", builder); + + assertEquals(NestedEnum.FOO, builder.getRepeatedNestedEnum(0)); + assertEquals(NestedEnum.BAR, builder.getRepeatedNestedEnum(1)); + assertEquals(NestedEnum.BAZ, builder.getRepeatedNestedEnum(2)); + assertEquals(3, builder.getRepeatedNestedEnumList().size()); + } + public void testParserIntegerEnumValue() throws Exception { TestAllTypes.Builder actualBuilder = TestAllTypes.newBuilder(); mergeFromJson("{\n" + " \"optionalNestedEnum\": 2\n" + "}", actualBuilder); @@ -1188,10 +1230,14 @@ public class JsonFormatTest extends TestCase { assertRoundTripEquals(message); } - public void testDefaultGsonDoesNotHtmlEscape() throws Exception { - TestAllTypes message = TestAllTypes.newBuilder().setOptionalString("=").build(); - assertEquals( - "{\n" + " \"optionalString\": \"=\"" + "\n}", JsonFormat.printer().print(message)); + // Regression test for b/73832901. Make sure html tags are escaped. + public void testHtmlEscape() throws Exception { + TestAllTypes message = TestAllTypes.newBuilder().setOptionalString("</script>").build(); + assertEquals("{\n \"optionalString\": \"\\u003c/script\\u003e\"\n}", toJsonString(message)); + + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + JsonFormat.parser().merge(toJsonString(message), builder); + assertEquals(message.getOptionalString(), builder.getOptionalString()); } public void testIncludingDefaultValueFields() throws Exception { |