From 3b3c8abb9635eb3ea078a821a99c9ef29d66dff7 Mon Sep 17 00:00:00 2001 From: Jisi Liu Date: Wed, 30 Mar 2016 11:39:59 -0700 Subject: Integrate google internal changes. --- .../com/google/protobuf/util/FieldMaskTree.java | 75 ++++--- .../java/com/google/protobuf/util/JsonFormat.java | 26 ++- .../google/protobuf/util/FieldMaskTreeTest.java | 124 ++++++----- .../com/google/protobuf/util/JsonFormatTest.java | 240 +++++++++++---------- .../proto/com/google/protobuf/util/json_test.proto | 31 +++ 5 files changed, 294 insertions(+), 202 deletions(-) (limited to 'java/util') 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 dc2f4b84..d6a2f6fa 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 @@ -60,10 +60,9 @@ import java.util.logging.Logger; * FieldMask in a message tree. */ class FieldMaskTree { - private static final Logger logger = - Logger.getLogger(FieldMaskTree.class.getName()); - - private static final String FIELD_PATH_SEPARATOR_REGEX = "\\."; + private static final Logger logger = Logger.getLogger(FieldMaskTree.class.getName()); + + private static final String FIELD_PATH_SEPARATOR_REGEX = "\\."; private static class Node { public TreeMap children = new TreeMap(); @@ -73,12 +72,12 @@ class FieldMaskTree { /** Creates an empty FieldMaskTree. */ public FieldMaskTree() {} - + /** Creates a FieldMaskTree for a given FieldMask. */ public FieldMaskTree(FieldMask mask) { mergeFromFieldMask(mask); } - + @Override public String toString() { return FieldMaskUtil.toString(toFieldMask()); @@ -121,7 +120,7 @@ class FieldMaskTree { node.children.clear(); return this; } - + /** * Merges all field paths in a FieldMask into this tree. */ @@ -149,8 +148,7 @@ class FieldMaskTree { return; } for (Entry entry : node.children.entrySet()) { - String childPath = path.isEmpty() - ? entry.getKey() : path + "." + entry.getKey(); + String childPath = path.isEmpty() ? entry.getKey() : path + "." + entry.getKey(); getFieldPaths(entry.getValue(), childPath, paths); } } @@ -193,11 +191,10 @@ class FieldMaskTree { * Merges all fields specified by this FieldMaskTree from {@code source} to * {@code destination}. */ - public void merge(Message source, Message.Builder destination, - FieldMaskUtil.MergeOptions options) { + public void merge( + Message source, Message.Builder destination, FieldMaskUtil.MergeOptions options) { if (source.getDescriptorForType() != destination.getDescriptorForType()) { - throw new IllegalArgumentException( - "Cannot merge messages of different types."); + throw new IllegalArgumentException("Cannot merge messages of different types."); } if (root.children.isEmpty()) { return; @@ -208,30 +205,41 @@ class FieldMaskTree { /** Merges all fields specified by a sub-tree from {@code source} to * {@code destination}. */ - private void merge(Node node, String path, Message source, - Message.Builder destination, FieldMaskUtil.MergeOptions options) { + private void merge( + Node node, + String path, + Message source, + Message.Builder destination, + FieldMaskUtil.MergeOptions options) { assert source.getDescriptorForType() == destination.getDescriptorForType(); - + Descriptor descriptor = source.getDescriptorForType(); for (Entry entry : node.children.entrySet()) { - FieldDescriptor field = - descriptor.findFieldByName(entry.getKey()); + FieldDescriptor field = descriptor.findFieldByName(entry.getKey()); if (field == null) { - logger.warning("Cannot find field \"" + entry.getKey() - + "\" in message type " + descriptor.getFullName()); + logger.warning( + "Cannot find field \"" + + entry.getKey() + + "\" in message type " + + descriptor.getFullName()); continue; } if (!entry.getValue().children.isEmpty()) { - if (field.isRepeated() - || field.getJavaType() != FieldDescriptor.JavaType.MESSAGE) { - logger.warning("Field \"" + field.getFullName() + "\" is not a " - + "singluar message field and cannot have sub-fields."); + if (field.isRepeated() || field.getJavaType() != FieldDescriptor.JavaType.MESSAGE) { + logger.warning( + "Field \"" + + field.getFullName() + + "\" is not a " + + "singluar message field and cannot have sub-fields."); continue; } - String childPath = path.isEmpty() - ? entry.getKey() : path + "." + entry.getKey(); - merge(entry.getValue(), childPath, (Message) source.getField(field), - destination.getFieldBuilder(field), options); + String childPath = path.isEmpty() ? entry.getKey() : path + "." + entry.getKey(); + merge( + entry.getValue(), + childPath, + (Message) source.getField(field), + destination.getFieldBuilder(field), + options); continue; } if (field.isRepeated()) { @@ -245,10 +253,15 @@ class FieldMaskTree { } else { if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { if (options.replaceMessageFields()) { - destination.setField(field, source.getField(field)); + if (!source.hasField(field)) { + destination.clearField(field); + } else { + destination.setField(field, source.getField(field)); + } } else { - destination.getFieldBuilder(field).mergeFrom( - (Message) source.getField(field)); + if (source.hasField(field)) { + destination.getFieldBuilder(field).mergeFrom((Message) source.getField(field)); + } } } else { destination.setField(field, source.getField(field)); 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 d13ff0ed..d7bf34a4 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 @@ -32,6 +32,7 @@ package com.google.protobuf.util; import com.google.common.io.BaseEncoding; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonNull; @@ -433,7 +434,7 @@ public class JsonFormat { private final Gson gson; private static class GsonHolder { - private static final Gson DEFAULT_GSON = new Gson(); + private static final Gson DEFAULT_GSON = new GsonBuilder().disableHtmlEscaping().create(); } PrinterImpl( @@ -1066,6 +1067,15 @@ public class JsonFormat { parser.mergeStruct(json, builder); } }); + // Special-case ListValue. + parsers.put(ListValue.getDescriptor().getFullName(), + new WellKnownTypeParser() { + @Override + public void merge(ParserImpl parser, JsonElement json, + Message.Builder builder) throws InvalidProtocolBufferException { + parser.mergeListValue(json, builder); + } + }); // Special-case Value. parsers.put(Value.getDescriptor().getFullName(), new WellKnownTypeParser() { @@ -1213,6 +1223,16 @@ public class JsonFormat { } mergeMapField(field, json, builder); } + + private void mergeListValue(JsonElement json, Message.Builder builder) + throws InvalidProtocolBufferException { + Descriptor descriptor = builder.getDescriptorForType(); + FieldDescriptor field = descriptor.findFieldByName("values"); + if (field == null) { + throw new InvalidProtocolBufferException("Invalid ListValue type."); + } + mergeRepeatedField(field, json, builder); + } private void mergeValue(JsonElement json, Message.Builder builder) throws InvalidProtocolBufferException { @@ -1237,9 +1257,7 @@ public class JsonFormat { } else if (json instanceof JsonArray) { FieldDescriptor field = type.findFieldByName("list_value"); Message.Builder listBuilder = builder.newBuilderForField(field); - FieldDescriptor listField = - listBuilder.getDescriptorForType().findFieldByName("values"); - mergeRepeatedField(listField, json, listBuilder); + merge(json, listBuilder); builder.setField(field, listBuilder.build()); } else { throw new IllegalStateException("Unexpected json data: " + json); 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 3391f239..618e0072 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 @@ -61,19 +61,16 @@ public class FieldMaskTreeTest extends TestCase { tree.addFieldPath("bar"); assertEquals("bar,foo", tree.toString()); } - + public void testMergeFromFieldMask() throws Exception { - FieldMaskTree tree = new FieldMaskTree( - FieldMaskUtil.fromString("foo,bar.baz,bar.quz")); + FieldMaskTree tree = new FieldMaskTree(FieldMaskUtil.fromString("foo,bar.baz,bar.quz")); assertEquals("bar.baz,bar.quz,foo", tree.toString()); - tree.mergeFromFieldMask( - FieldMaskUtil.fromString("foo.bar,bar")); + tree.mergeFromFieldMask(FieldMaskUtil.fromString("foo.bar,bar")); assertEquals("bar,foo", tree.toString()); } - + public void testIntersectFieldPath() throws Exception { - FieldMaskTree tree = new FieldMaskTree( - FieldMaskUtil.fromString("foo,bar.baz,bar.quz")); + FieldMaskTree tree = new FieldMaskTree(FieldMaskUtil.fromString("foo,bar.baz,bar.quz")); FieldMaskTree result = new FieldMaskTree(); // Empty path. tree.intersectFieldPath("", result); @@ -96,16 +93,18 @@ public class FieldMaskTreeTest extends TestCase { } public void testMerge() throws Exception { - TestAllTypes value = TestAllTypes.newBuilder() - .setOptionalInt32(1234) - .setOptionalNestedMessage(NestedMessage.newBuilder().setBb(5678)) - .addRepeatedInt32(4321) - .addRepeatedNestedMessage(NestedMessage.newBuilder().setBb(8765)) - .build(); - NestedTestAllTypes source = NestedTestAllTypes.newBuilder() - .setPayload(value) - .setChild(NestedTestAllTypes.newBuilder().setPayload(value)) - .build(); + TestAllTypes value = + TestAllTypes.newBuilder() + .setOptionalInt32(1234) + .setOptionalNestedMessage(NestedMessage.newBuilder().setBb(5678)) + .addRepeatedInt32(4321) + .addRepeatedNestedMessage(NestedMessage.newBuilder().setBb(8765)) + .build(); + NestedTestAllTypes source = + NestedTestAllTypes.newBuilder() + .setPayload(value) + .setChild(NestedTestAllTypes.newBuilder().setPayload(value)) + .build(); // Now we have a message source with the following structure: // [root] -+- payload -+- optional_int32 // | +- optional_nested_message @@ -116,114 +115,127 @@ public class FieldMaskTreeTest extends TestCase { // +- optional_nested_message // +- repeated_int32 // +- repeated_nested_message - + FieldMaskUtil.MergeOptions options = new FieldMaskUtil.MergeOptions(); - + // Test merging each individual field. NestedTestAllTypes.Builder builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree().addFieldPath("payload.optional_int32") - .merge(source, builder, options); + new FieldMaskTree().addFieldPath("payload.optional_int32").merge(source, builder, options); NestedTestAllTypes.Builder expected = NestedTestAllTypes.newBuilder(); expected.getPayloadBuilder().setOptionalInt32(1234); assertEquals(expected.build(), builder.build()); builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree().addFieldPath("payload.optional_nested_message") + new FieldMaskTree() + .addFieldPath("payload.optional_nested_message") .merge(source, builder, options); expected = NestedTestAllTypes.newBuilder(); - expected.getPayloadBuilder().setOptionalNestedMessage( - NestedMessage.newBuilder().setBb(5678)); + 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); + new FieldMaskTree().addFieldPath("payload.repeated_int32").merge(source, builder, options); expected = NestedTestAllTypes.newBuilder(); expected.getPayloadBuilder().addRepeatedInt32(4321); assertEquals(expected.build(), builder.build()); builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree().addFieldPath("payload.repeated_nested_message") + new FieldMaskTree() + .addFieldPath("payload.repeated_nested_message") .merge(source, builder, options); expected = NestedTestAllTypes.newBuilder(); - expected.getPayloadBuilder().addRepeatedNestedMessage( - NestedMessage.newBuilder().setBb(8765)); + expected.getPayloadBuilder().addRepeatedNestedMessage(NestedMessage.newBuilder().setBb(8765)); assertEquals(expected.build(), builder.build()); builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree().addFieldPath("child.payload.optional_int32") + new FieldMaskTree() + .addFieldPath("child.payload.optional_int32") .merge(source, builder, options); expected = NestedTestAllTypes.newBuilder(); expected.getChildBuilder().getPayloadBuilder().setOptionalInt32(1234); assertEquals(expected.build(), builder.build()); builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree().addFieldPath("child.payload.optional_nested_message") + new FieldMaskTree() + .addFieldPath("child.payload.optional_nested_message") .merge(source, builder, options); expected = NestedTestAllTypes.newBuilder(); - expected.getChildBuilder().getPayloadBuilder().setOptionalNestedMessage( - NestedMessage.newBuilder().setBb(5678)); + expected + .getChildBuilder() + .getPayloadBuilder() + .setOptionalNestedMessage(NestedMessage.newBuilder().setBb(5678)); assertEquals(expected.build(), builder.build()); - builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree().addFieldPath("child.payload.repeated_int32") + new FieldMaskTree() + .addFieldPath("child.payload.repeated_int32") .merge(source, builder, options); expected = NestedTestAllTypes.newBuilder(); expected.getChildBuilder().getPayloadBuilder().addRepeatedInt32(4321); assertEquals(expected.build(), builder.build()); - builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree().addFieldPath("child.payload.repeated_nested_message") + new FieldMaskTree() + .addFieldPath("child.payload.repeated_nested_message") .merge(source, builder, options); expected = NestedTestAllTypes.newBuilder(); - expected.getChildBuilder().getPayloadBuilder().addRepeatedNestedMessage( - NestedMessage.newBuilder().setBb(8765)); + expected + .getChildBuilder() + .getPayloadBuilder() + .addRepeatedNestedMessage(NestedMessage.newBuilder().setBb(8765)); assertEquals(expected.build(), builder.build()); - + // Test merging all fields. builder = NestedTestAllTypes.newBuilder(); - new FieldMaskTree().addFieldPath("child").addFieldPath("payload") - .merge(source, builder, options); + new FieldMaskTree() + .addFieldPath("child") + .addFieldPath("payload") + .merge(source, builder, options); assertEquals(source, builder.build()); - + // Test repeated options. builder = NestedTestAllTypes.newBuilder(); builder.getPayloadBuilder().addRepeatedInt32(1000); - new FieldMaskTree().addFieldPath("payload.repeated_int32") - .merge(source, builder, options); + new FieldMaskTree().addFieldPath("payload.repeated_int32").merge(source, builder, options); // 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); + new FieldMaskTree().addFieldPath("payload.repeated_int32").merge(source, builder, options); assertEquals(1, builder.getPayload().getRepeatedInt32Count()); assertEquals(4321, builder.getPayload().getRepeatedInt32(0)); - + // Test message options. builder = NestedTestAllTypes.newBuilder(); builder.getPayloadBuilder().setOptionalInt32(1000); builder.getPayloadBuilder().setOptionalUint32(2000); - new FieldMaskTree().addFieldPath("payload") - .merge(source, builder, options); + new FieldMaskTree().addFieldPath("payload").merge(source, builder, options); // Default behavior is to merge message fields. assertEquals(1234, builder.getPayload().getOptionalInt32()); assertEquals(2000, builder.getPayload().getOptionalUint32()); - + + // Test merging unset message fields. + NestedTestAllTypes clearedSource = source.toBuilder().clearPayload().build(); + builder = NestedTestAllTypes.newBuilder(); + new FieldMaskTree().addFieldPath("payload").merge(clearedSource, builder, options); + assertEquals(false, builder.hasPayload()); + // Change to replace message fields. options.setReplaceMessageFields(true); builder = NestedTestAllTypes.newBuilder(); builder.getPayloadBuilder().setOptionalInt32(1000); builder.getPayloadBuilder().setOptionalUint32(2000); - new FieldMaskTree().addFieldPath("payload") - .merge(source, builder, options); + new FieldMaskTree().addFieldPath("payload").merge(source, builder, options); assertEquals(1234, builder.getPayload().getOptionalInt32()); assertEquals(0, builder.getPayload().getOptionalUint32()); + + // Test merging unset message fields. + builder = NestedTestAllTypes.newBuilder(); + builder.getPayloadBuilder().setOptionalInt32(1000); + builder.getPayloadBuilder().setOptionalUint32(2000); + new FieldMaskTree().addFieldPath("payload").merge(clearedSource, builder, options); + assertEquals(false, builder.hasPayload()); } } - 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 c0eb0330..d95b626c 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 @@ -122,11 +122,11 @@ public class JsonFormatTest extends TestCase { builder.addRepeatedNestedEnum(NestedEnum.BAZ); builder.addRepeatedNestedMessageBuilder().setValue(200); } - + private void assertRoundTripEquals(Message message) throws Exception { assertRoundTripEquals(message, TypeRegistry.getEmptyTypeRegistry()); } - + private void assertRoundTripEquals(Message message, TypeRegistry registry) throws Exception { JsonFormat.Printer printer = JsonFormat.printer().usingTypeRegistry(registry); JsonFormat.Parser parser = JsonFormat.parser().usingTypeRegistry(registry); @@ -135,68 +135,68 @@ public class JsonFormatTest extends TestCase { Message parsedMessage = builder.build(); assertEquals(message.toString(), parsedMessage.toString()); } - + private String toJsonString(Message message) throws IOException { return JsonFormat.printer().print(message); } - + private void mergeFromJson(String json, Message.Builder builder) throws IOException { JsonFormat.parser().merge(json, builder); } - + public void testAllFields() throws Exception { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); setAllFields(builder); TestAllTypes message = builder.build(); - - assertEquals( + + assertEquals( "{\n" - + " \"optionalInt32\": 1234,\n" - + " \"optionalInt64\": \"1234567890123456789\",\n" - + " \"optionalUint32\": 5678,\n" - + " \"optionalUint64\": \"2345678901234567890\",\n" - + " \"optionalSint32\": 9012,\n" - + " \"optionalSint64\": \"3456789012345678901\",\n" - + " \"optionalFixed32\": 3456,\n" - + " \"optionalFixed64\": \"4567890123456789012\",\n" - + " \"optionalSfixed32\": 7890,\n" - + " \"optionalSfixed64\": \"5678901234567890123\",\n" - + " \"optionalFloat\": 1.5,\n" - + " \"optionalDouble\": 1.25,\n" - + " \"optionalBool\": true,\n" - + " \"optionalString\": \"Hello world!\",\n" - + " \"optionalBytes\": \"AAEC\",\n" - + " \"optionalNestedMessage\": {\n" - + " \"value\": 100\n" - + " },\n" - + " \"optionalNestedEnum\": \"BAR\",\n" - + " \"repeatedInt32\": [1234, 234],\n" - + " \"repeatedInt64\": [\"1234567890123456789\", \"234567890123456789\"],\n" - + " \"repeatedUint32\": [5678, 678],\n" - + " \"repeatedUint64\": [\"2345678901234567890\", \"345678901234567890\"],\n" - + " \"repeatedSint32\": [9012, 10],\n" - + " \"repeatedSint64\": [\"3456789012345678901\", \"456789012345678901\"],\n" - + " \"repeatedFixed32\": [3456, 456],\n" - + " \"repeatedFixed64\": [\"4567890123456789012\", \"567890123456789012\"],\n" - + " \"repeatedSfixed32\": [7890, 890],\n" - + " \"repeatedSfixed64\": [\"5678901234567890123\", \"678901234567890123\"],\n" - + " \"repeatedFloat\": [1.5, 11.5],\n" - + " \"repeatedDouble\": [1.25, 11.25],\n" - + " \"repeatedBool\": [true, true],\n" - + " \"repeatedString\": [\"Hello world!\", \"ello world!\"],\n" - + " \"repeatedBytes\": [\"AAEC\", \"AQI=\"],\n" - + " \"repeatedNestedMessage\": [{\n" - + " \"value\": 100\n" - + " }, {\n" - + " \"value\": 200\n" - + " }],\n" - + " \"repeatedNestedEnum\": [\"BAR\", \"BAZ\"]\n" - + "}", + + " \"optionalInt32\": 1234,\n" + + " \"optionalInt64\": \"1234567890123456789\",\n" + + " \"optionalUint32\": 5678,\n" + + " \"optionalUint64\": \"2345678901234567890\",\n" + + " \"optionalSint32\": 9012,\n" + + " \"optionalSint64\": \"3456789012345678901\",\n" + + " \"optionalFixed32\": 3456,\n" + + " \"optionalFixed64\": \"4567890123456789012\",\n" + + " \"optionalSfixed32\": 7890,\n" + + " \"optionalSfixed64\": \"5678901234567890123\",\n" + + " \"optionalFloat\": 1.5,\n" + + " \"optionalDouble\": 1.25,\n" + + " \"optionalBool\": true,\n" + + " \"optionalString\": \"Hello world!\",\n" + + " \"optionalBytes\": \"AAEC\",\n" + + " \"optionalNestedMessage\": {\n" + + " \"value\": 100\n" + + " },\n" + + " \"optionalNestedEnum\": \"BAR\",\n" + + " \"repeatedInt32\": [1234, 234],\n" + + " \"repeatedInt64\": [\"1234567890123456789\", \"234567890123456789\"],\n" + + " \"repeatedUint32\": [5678, 678],\n" + + " \"repeatedUint64\": [\"2345678901234567890\", \"345678901234567890\"],\n" + + " \"repeatedSint32\": [9012, 10],\n" + + " \"repeatedSint64\": [\"3456789012345678901\", \"456789012345678901\"],\n" + + " \"repeatedFixed32\": [3456, 456],\n" + + " \"repeatedFixed64\": [\"4567890123456789012\", \"567890123456789012\"],\n" + + " \"repeatedSfixed32\": [7890, 890],\n" + + " \"repeatedSfixed64\": [\"5678901234567890123\", \"678901234567890123\"],\n" + + " \"repeatedFloat\": [1.5, 11.5],\n" + + " \"repeatedDouble\": [1.25, 11.25],\n" + + " \"repeatedBool\": [true, true],\n" + + " \"repeatedString\": [\"Hello world!\", \"ello world!\"],\n" + + " \"repeatedBytes\": [\"AAEC\", \"AQI=\"],\n" + + " \"repeatedNestedMessage\": [{\n" + + " \"value\": 100\n" + + " }, {\n" + + " \"value\": 200\n" + + " }],\n" + + " \"repeatedNestedEnum\": [\"BAR\", \"BAZ\"]\n" + + "}", toJsonString(message)); - + assertRoundTripEquals(message); } - + public void testUnknownEnumValues() throws Exception { TestAllTypes message = TestAllTypes.newBuilder() .setOptionalNestedEnumValue(12345) @@ -209,21 +209,22 @@ public class JsonFormatTest extends TestCase { + " \"repeatedNestedEnum\": [12345, \"FOO\"]\n" + "}", toJsonString(message)); assertRoundTripEquals(message); - + TestMap.Builder mapBuilder = TestMap.newBuilder(); mapBuilder.getMutableInt32ToEnumMapValue().put(1, 0); mapBuilder.getMutableInt32ToEnumMapValue().put(2, 12345); TestMap mapMessage = mapBuilder.build(); assertEquals( - "{\n" - + " \"int32ToEnumMap\": {\n" - + " \"1\": \"FOO\",\n" - + " \"2\": 12345\n" - + " }\n" - + "}", toJsonString(mapMessage)); + "{\n" + + " \"int32ToEnumMap\": {\n" + + " \"1\": \"FOO\",\n" + + " \"2\": 12345\n" + + " }\n" + + "}", + toJsonString(mapMessage)); assertRoundTripEquals(mapMessage); } - + public void testSpecialFloatValues() throws Exception { TestAllTypes message = TestAllTypes.newBuilder() .addRepeatedFloat(Float.NaN) @@ -238,10 +239,10 @@ public class JsonFormatTest extends TestCase { + " \"repeatedFloat\": [\"NaN\", \"Infinity\", \"-Infinity\"],\n" + " \"repeatedDouble\": [\"NaN\", \"Infinity\", \"-Infinity\"]\n" + "}", toJsonString(message)); - + assertRoundTripEquals(message); } - + public void testParserAcceptStringForNumbericField() throws Exception { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); mergeFromJson( @@ -265,7 +266,7 @@ public class JsonFormatTest extends TestCase { assertEquals(1.25, message.getOptionalDouble()); assertEquals(true, message.getOptionalBool()); } - + public void testParserAcceptFloatingPointValueForIntegerField() throws Exception { // Test that numeric values like "1.000", "1e5" will also be accepted. TestAllTypes.Builder builder = TestAllTypes.newBuilder(); @@ -287,14 +288,14 @@ public class JsonFormatTest extends TestCase { assertEquals(expectedValues[i], builder.getRepeatedInt64(i)); assertEquals(expectedValues[i], builder.getRepeatedUint64(i)); } - + // Non-integers will still be rejected. assertRejects("optionalInt32", "1.5"); assertRejects("optionalUint32", "1.5"); assertRejects("optionalInt64", "1.5"); assertRejects("optionalUint64", "1.5"); } - + private void assertRejects(String name, String value) { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); try { @@ -312,7 +313,7 @@ public class JsonFormatTest extends TestCase { // Expected. } } - + private void assertAccepts(String name, String value) throws IOException { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); // Both numeric form and string form are accepted. @@ -320,17 +321,17 @@ public class JsonFormatTest extends TestCase { builder.clear(); mergeFromJson("{\"" + name + "\":\"" + value + "\"}", builder); } - + public void testParserRejectOutOfRangeNumericValues() throws Exception { assertAccepts("optionalInt32", String.valueOf(Integer.MAX_VALUE)); assertAccepts("optionalInt32", String.valueOf(Integer.MIN_VALUE)); assertRejects("optionalInt32", String.valueOf(Integer.MAX_VALUE + 1L)); assertRejects("optionalInt32", String.valueOf(Integer.MIN_VALUE - 1L)); - + assertAccepts("optionalUint32", String.valueOf(Integer.MAX_VALUE + 1L)); assertRejects("optionalUint32", "123456789012345"); assertRejects("optionalUint32", "-1"); - + BigInteger one = new BigInteger("1"); BigInteger maxLong = new BigInteger(String.valueOf(Long.MAX_VALUE)); BigInteger minLong = new BigInteger(String.valueOf(Long.MIN_VALUE)); @@ -351,7 +352,7 @@ public class JsonFormatTest extends TestCase { assertAccepts("optionalFloat", String.valueOf(-Float.MAX_VALUE)); assertRejects("optionalFloat", String.valueOf(Double.MAX_VALUE)); assertRejects("optionalFloat", String.valueOf(-Double.MAX_VALUE)); - + BigDecimal moreThanOne = new BigDecimal("1.000001"); BigDecimal maxDouble = new BigDecimal(Double.MAX_VALUE); BigDecimal minDouble = new BigDecimal(-Double.MAX_VALUE); @@ -360,7 +361,7 @@ public class JsonFormatTest extends TestCase { assertRejects("optionalDouble", maxDouble.multiply(moreThanOne).toString()); assertRejects("optionalDouble", minDouble.multiply(moreThanOne).toString()); } - + public void testParserAcceptNull() throws Exception { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); mergeFromJson( @@ -402,7 +403,7 @@ public class JsonFormatTest extends TestCase { + "}", builder); TestAllTypes message = builder.build(); assertEquals(TestAllTypes.getDefaultInstance(), message); - + // Repeated field elements cannot be null. try { builder = TestAllTypes.newBuilder(); @@ -414,7 +415,7 @@ public class JsonFormatTest extends TestCase { } catch (InvalidProtocolBufferException e) { // Exception expected. } - + try { builder = TestAllTypes.newBuilder(); mergeFromJson( @@ -426,14 +427,14 @@ public class JsonFormatTest extends TestCase { // Exception expected. } } - + public void testParserRejectDuplicatedFields() throws Exception { // TODO(xiaofeng): The parser we are currently using (GSON) will accept and keep the last // one if multiple entries have the same name. This is not the desired behavior but it can // only be fixed by using our own parser. Here we only test the cases where the names are // different but still referring to the same field. - - // Duplicated optional fields. + + // Duplicated optional fields. try { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); mergeFromJson( @@ -445,7 +446,7 @@ public class JsonFormatTest extends TestCase { } catch (InvalidProtocolBufferException e) { // Exception expected. } - + // Duplicated repeated fields. try { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); @@ -458,7 +459,7 @@ public class JsonFormatTest extends TestCase { } catch (InvalidProtocolBufferException e) { // Exception expected. } - + // Duplicated oneof fields. try { TestOneof.Builder builder = TestOneof.newBuilder(); @@ -472,7 +473,7 @@ public class JsonFormatTest extends TestCase { // Exception expected. } } - + public void testMapFields() throws Exception { TestMap.Builder builder = TestMap.newBuilder(); builder.getMutableInt32ToInt32Map().put(1, 10); @@ -507,7 +508,7 @@ public class JsonFormatTest extends TestCase { 8, NestedMessage.newBuilder().setValue(1234).build()); builder.getMutableInt32ToEnumMap().put(9, NestedEnum.BAR); TestMap message = builder.build(); - + assertEquals( "{\n" + " \"int32ToInt32Map\": {\n" @@ -598,13 +599,13 @@ public class JsonFormatTest extends TestCase { + " }\n" + "}", toJsonString(message)); assertRoundTripEquals(message); - + // Test multiple entries. builder = TestMap.newBuilder(); builder.getMutableInt32ToInt32Map().put(1, 2); builder.getMutableInt32ToInt32Map().put(3, 4); message = builder.build(); - + assertEquals( "{\n" + " \"int32ToInt32Map\": {\n" @@ -614,7 +615,7 @@ public class JsonFormatTest extends TestCase { + "}", toJsonString(message)); assertRoundTripEquals(message); } - + public void testMapNullValueIsRejected() throws Exception { try { TestMap.Builder builder = TestMap.newBuilder(); @@ -627,7 +628,7 @@ public class JsonFormatTest extends TestCase { } catch (InvalidProtocolBufferException e) { // Exception expected. } - + try { TestMap.Builder builder = TestMap.newBuilder(); mergeFromJson( @@ -640,7 +641,7 @@ public class JsonFormatTest extends TestCase { // Exception expected. } } - + public void testParserAcceptNonQuotedObjectKey() throws Exception { TestMap.Builder builder = TestMap.newBuilder(); mergeFromJson( @@ -652,7 +653,7 @@ public class JsonFormatTest extends TestCase { assertEquals(2, message.getInt32ToInt32Map().get(1).intValue()); assertEquals(3, message.getStringToInt32Map().get("hello").intValue()); } - + public void testWrappers() throws Exception { TestWrappers.Builder builder = TestWrappers.newBuilder(); builder.getBoolValueBuilder().setValue(false); @@ -665,7 +666,7 @@ public class JsonFormatTest extends TestCase { builder.getStringValueBuilder().setValue(""); builder.getBytesValueBuilder().setValue(ByteString.EMPTY); TestWrappers message = builder.build(); - + assertEquals( "{\n" + " \"int32Value\": 0,\n" @@ -691,7 +692,7 @@ public class JsonFormatTest extends TestCase { builder.getStringValueBuilder().setValue("7"); builder.getBytesValueBuilder().setValue(ByteString.copyFrom(new byte[]{8})); message = builder.build(); - + assertEquals( "{\n" + " \"int32Value\": 1,\n" @@ -706,43 +707,43 @@ public class JsonFormatTest extends TestCase { + "}", toJsonString(message)); assertRoundTripEquals(message); } - + public void testTimestamp() throws Exception { TestTimestamp message = TestTimestamp.newBuilder() .setTimestampValue(TimeUtil.parseTimestamp("1970-01-01T00:00:00Z")) .build(); - + assertEquals( "{\n" + " \"timestampValue\": \"1970-01-01T00:00:00Z\"\n" + "}", toJsonString(message)); assertRoundTripEquals(message); } - + public void testDuration() throws Exception { TestDuration message = TestDuration.newBuilder() .setDurationValue(TimeUtil.parseDuration("12345s")) .build(); - + assertEquals( "{\n" + " \"durationValue\": \"12345s\"\n" + "}", toJsonString(message)); assertRoundTripEquals(message); } - + public void testFieldMask() throws Exception { TestFieldMask message = TestFieldMask.newBuilder() .setFieldMaskValue(FieldMaskUtil.fromString("foo.bar,baz")) .build(); - + assertEquals( "{\n" + " \"fieldMaskValue\": \"foo.bar,baz\"\n" + "}", toJsonString(message)); assertRoundTripEquals(message); } - + public void testStruct() throws Exception { // Build a struct with all possible values. TestStruct.Builder builder = TestStruct.newBuilder(); @@ -764,7 +765,7 @@ public class JsonFormatTest extends TestCase { structBuilder.getMutableFields().put( "list_value", Value.newBuilder().setListValue(listBuilder.build()).build()); TestStruct message = builder.build(); - + assertEquals( "{\n" + " \"structValue\": {\n" @@ -778,7 +779,7 @@ public class JsonFormatTest extends TestCase { + " }\n" + "}", toJsonString(message)); assertRoundTripEquals(message); - + builder = TestStruct.newBuilder(); builder.setValue(Value.newBuilder().setNullValueValue(0).build()); message = builder.build(); @@ -787,12 +788,23 @@ public class JsonFormatTest extends TestCase { + " \"value\": null\n" + "}", toJsonString(message)); assertRoundTripEquals(message); + + builder = TestStruct.newBuilder(); + listBuilder = builder.getListValueBuilder(); + listBuilder.addValues(Value.newBuilder().setNumberValue(31831.125).build()); + listBuilder.addValues(Value.newBuilder().setNullValueValue(0).build()); + message = builder.build(); + assertEquals( + "{\n" + + " \"listValue\": [31831.125, null]\n" + + "}", toJsonString(message)); + assertRoundTripEquals(message); } - + public void testAnyFields() throws Exception { TestAllTypes content = TestAllTypes.newBuilder().setOptionalInt32(1234).build(); TestAny message = TestAny.newBuilder().setAnyValue(Any.pack(content)).build(); - + // A TypeRegistry must be provided in order to convert Any types. try { toJsonString(message); @@ -800,11 +812,11 @@ public class JsonFormatTest extends TestCase { } catch (IOException e) { // Expected. } - + JsonFormat.TypeRegistry registry = JsonFormat.TypeRegistry.newBuilder() .add(TestAllTypes.getDescriptor()).build(); JsonFormat.Printer printer = JsonFormat.printer().usingTypeRegistry(registry); - + assertEquals( "{\n" + " \"anyValue\": {\n" @@ -813,8 +825,8 @@ public class JsonFormatTest extends TestCase { + " }\n" + "}" , printer.print(message)); assertRoundTripEquals(message, registry); - - + + // Well-known types have a special formatting when embedded in Any. // // 1. Any in Any. @@ -828,7 +840,7 @@ public class JsonFormatTest extends TestCase { + " }\n" + "}", printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); - + // 2. Wrappers in Any. anyMessage = Any.pack(Int32Value.newBuilder().setValue(12345).build()); assertEquals( @@ -894,7 +906,7 @@ public class JsonFormatTest extends TestCase { + " \"value\": \"AQI=\"\n" + "}", printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); - + // 3. Timestamp in Any. anyMessage = Any.pack(TimeUtil.parseTimestamp("1969-12-31T23:59:59Z")); assertEquals( @@ -903,7 +915,7 @@ public class JsonFormatTest extends TestCase { + " \"value\": \"1969-12-31T23:59:59Z\"\n" + "}", printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); - + // 4. Duration in Any anyMessage = Any.pack(TimeUtil.parseDuration("12345.10s")); assertEquals( @@ -945,7 +957,7 @@ public class JsonFormatTest extends TestCase { + "}", printer.print(anyMessage)); assertRoundTripEquals(anyMessage, registry); } - + public void testParserMissingTypeUrl() throws Exception { try { Any.Builder builder = Any.newBuilder(); @@ -958,7 +970,7 @@ public class JsonFormatTest extends TestCase { // Expected. } } - + public void testParserUnexpectedTypeUrl() throws Exception { try { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); @@ -970,9 +982,9 @@ public class JsonFormatTest extends TestCase { fail("Exception is expected."); } catch (IOException e) { // Expected. - } + } } - + public void testParserRejectTrailingComma() throws Exception { try { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); @@ -1000,13 +1012,13 @@ public class JsonFormatTest extends TestCase { // // Expected. // } } - + public void testParserRejectInvalidBase64() throws Exception { assertRejects("optionalBytes", "!@#$"); // We use standard BASE64 with paddings. assertRejects("optionalBytes", "AQI"); } - + public void testParserRejectInvalidEnumValue() throws Exception { try { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); @@ -1017,7 +1029,7 @@ public class JsonFormatTest extends TestCase { fail("Exception is expected."); } catch (InvalidProtocolBufferException e) { // Expected. - } + } } public void testCustomJsonName() throws Exception { @@ -1026,6 +1038,12 @@ 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)); + } + public void testIncludingDefaultValueFields() throws Exception { TestAllTypes message = TestAllTypes.getDefaultInstance(); assertEquals("{\n}", JsonFormat.printer().print(message)); diff --git a/java/util/src/test/proto/com/google/protobuf/util/json_test.proto b/java/util/src/test/proto/com/google/protobuf/util/json_test.proto index 509c1d69..686edc42 100644 --- a/java/util/src/test/proto/com/google/protobuf/util/json_test.proto +++ b/java/util/src/test/proto/com/google/protobuf/util/json_test.proto @@ -28,6 +28,36 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + syntax = "proto3"; package json_test; @@ -159,6 +189,7 @@ message TestFieldMask { message TestStruct { google.protobuf.Struct struct_value = 1; google.protobuf.Value value = 2; + google.protobuf.ListValue list_value = 3; } message TestAny { -- cgit v1.2.3