From 60b71498d70a5645324385269c518b95c8c2feb0 Mon Sep 17 00:00:00 2001 From: Protobuf Team Bot Date: Mon, 7 Nov 2022 13:30:29 -0800 Subject: [PATCH] Add exemplar variants of the Java Any.is() and Any.unpack() methods. The Java Any.is() and Any.unpack() methods now accept an exemplar message in place of a Java class. This avoids the need to use Java introspection in the implementation of these methods. The exemplar variant of Any.is() is named Any.isSameTypeAs(). The exemplar variant of Any.unpack() is named Any.unpackSameTypeAs(). PiperOrigin-RevId: 486748727 --- .../java/com/google/protobuf/AnyTest.java | 96 ++++++++++++++++++- objectivec/GPBAny.pbobjc.h | 4 + src/google/protobuf/any.proto | 4 + src/google/protobuf/compiler/java/message.cc | 36 ++++++- 4 files changed, 136 insertions(+), 4 deletions(-) diff --git a/java/core/src/test/java/com/google/protobuf/AnyTest.java b/java/core/src/test/java/com/google/protobuf/AnyTest.java index ac047a9cf9..11b214b4b7 100644 --- a/java/core/src/test/java/com/google/protobuf/AnyTest.java +++ b/java/core/src/test/java/com/google/protobuf/AnyTest.java @@ -43,7 +43,6 @@ import org.junit.runners.JUnit4; /** Unit tests for Any message. */ @RunWith(JUnit4.class) public class AnyTest { - @Test public void testAnyGeneratedApi() throws Exception { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); @@ -78,6 +77,40 @@ public class AnyTest { } } + @Test + public void testAnyGeneratedExemplarApi() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestUtil.setAllFields(builder); + TestAllTypes message = builder.build(); + + TestAny container = TestAny.newBuilder().setValue(Any.pack(message)).build(); + + assertThat(container.getValue().isSameTypeAs(TestAllTypes.getDefaultInstance())).isTrue(); + assertThat(container.getValue().isSameTypeAs(TestAny.getDefaultInstance())).isFalse(); + + TestAllTypes result = container.getValue().unpackSameTypeAs(TestAllTypes.getDefaultInstance()); + TestUtil.assertAllFieldsSet(result); + + // Unpacking to a wrong exemplar will throw an exception. + try { + container.getValue().unpackSameTypeAs(TestAny.getDefaultInstance()); + assertWithMessage("Exception is expected.").fail(); + } catch (InvalidProtocolBufferException e) { + // expected. + } + + // Test that unpacking throws an exception if parsing fails. + TestAny.Builder containerBuilder = container.toBuilder(); + containerBuilder.getValueBuilder().setValue(ByteString.copyFrom(new byte[] {0x11})); + container = containerBuilder.build(); + try { + container.getValue().unpackSameTypeAs(TestAllTypes.getDefaultInstance()); + assertWithMessage("Exception is expected.").fail(); + } catch (InvalidProtocolBufferException e) { + // expected. + } + } + @Test public void testCustomTypeUrls() throws Exception { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); @@ -90,7 +123,9 @@ public class AnyTest { .isEqualTo("xxx.com/" + TestAllTypes.getDescriptor().getFullName()); assertThat(container.getValue().is(TestAllTypes.class)).isTrue(); + assertThat(container.getValue().isSameTypeAs(TestAllTypes.getDefaultInstance())).isTrue(); assertThat(container.getValue().is(TestAny.class)).isFalse(); + assertThat(container.getValue().isSameTypeAs(TestAny.getDefaultInstance())).isFalse(); TestAllTypes result = container.getValue().unpack(TestAllTypes.class); TestUtil.assertAllFieldsSet(result); @@ -101,7 +136,9 @@ public class AnyTest { .isEqualTo("yyy.com/" + TestAllTypes.getDescriptor().getFullName()); assertThat(container.getValue().is(TestAllTypes.class)).isTrue(); + assertThat(container.getValue().isSameTypeAs(TestAllTypes.getDefaultInstance())).isTrue(); assertThat(container.getValue().is(TestAny.class)).isFalse(); + assertThat(container.getValue().isSameTypeAs(TestAny.getDefaultInstance())).isFalse(); result = container.getValue().unpack(TestAllTypes.class); TestUtil.assertAllFieldsSet(result); @@ -112,12 +149,54 @@ public class AnyTest { .isEqualTo("/" + TestAllTypes.getDescriptor().getFullName()); assertThat(container.getValue().is(TestAllTypes.class)).isTrue(); + assertThat(container.getValue().isSameTypeAs(TestAllTypes.getDefaultInstance())).isTrue(); assertThat(container.getValue().is(TestAny.class)).isFalse(); + assertThat(container.getValue().isSameTypeAs(TestAny.getDefaultInstance())).isFalse(); result = container.getValue().unpack(TestAllTypes.class); TestUtil.assertAllFieldsSet(result); } + @Test + public void testCustomTypeUrlsWithExemplars() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestUtil.setAllFields(builder); + TestAllTypes message = builder.build(); + + TestAny container = TestAny.newBuilder().setValue(Any.pack(message, "xxx.com")).build(); + + assertThat(container.getValue().getTypeUrl()) + .isEqualTo("xxx.com/" + TestAllTypes.getDescriptor().getFullName()); + + assertThat(container.getValue().isSameTypeAs(TestAllTypes.getDefaultInstance())).isTrue(); + assertThat(container.getValue().isSameTypeAs(TestAny.getDefaultInstance())).isFalse(); + + TestAllTypes result = container.getValue().unpackSameTypeAs(TestAllTypes.getDefaultInstance()); + TestUtil.assertAllFieldsSet(result); + + container = TestAny.newBuilder().setValue(Any.pack(message, "yyy.com/")).build(); + + assertThat(container.getValue().getTypeUrl()) + .isEqualTo("yyy.com/" + TestAllTypes.getDescriptor().getFullName()); + + assertThat(container.getValue().isSameTypeAs(TestAllTypes.getDefaultInstance())).isTrue(); + assertThat(container.getValue().isSameTypeAs(TestAny.getDefaultInstance())).isFalse(); + + result = container.getValue().unpackSameTypeAs(TestAllTypes.getDefaultInstance()); + TestUtil.assertAllFieldsSet(result); + + container = TestAny.newBuilder().setValue(Any.pack(message, "")).build(); + + assertThat(container.getValue().getTypeUrl()) + .isEqualTo("/" + TestAllTypes.getDescriptor().getFullName()); + + assertThat(container.getValue().isSameTypeAs(TestAllTypes.getDefaultInstance())).isTrue(); + assertThat(container.getValue().isSameTypeAs(TestAny.getDefaultInstance())).isFalse(); + + result = container.getValue().unpackSameTypeAs(TestAllTypes.getDefaultInstance()); + TestUtil.assertAllFieldsSet(result); + } + @Test public void testCachedUnpackResult() throws Exception { TestAllTypes.Builder builder = TestAllTypes.newBuilder(); @@ -132,4 +211,19 @@ public class AnyTest { TestAllTypes result2 = container.getValue().unpack(TestAllTypes.class); assertThat(Objects.equals(result1, result2)).isTrue(); } + + @Test + public void testCachedUnpackExemplarResult() throws Exception { + TestAllTypes.Builder builder = TestAllTypes.newBuilder(); + TestUtil.setAllFields(builder); + TestAllTypes message = builder.build(); + + TestAny container = TestAny.newBuilder().setValue(Any.pack(message)).build(); + + assertThat(container.getValue().isSameTypeAs(TestAllTypes.getDefaultInstance())).isTrue(); + + TestAllTypes result1 = container.getValue().unpackSameTypeAs(TestAllTypes.getDefaultInstance()); + TestAllTypes result2 = container.getValue().unpackSameTypeAs(TestAllTypes.getDefaultInstance()); + assertThat(Objects.equals(result1, result2)).isTrue(); + } } diff --git a/objectivec/GPBAny.pbobjc.h b/objectivec/GPBAny.pbobjc.h index 3edf349865..4720bfdd7f 100644 --- a/objectivec/GPBAny.pbobjc.h +++ b/objectivec/GPBAny.pbobjc.h @@ -69,6 +69,10 @@ typedef GPB_ENUM(GPBAny_FieldNumber) { * if (any.is(Foo.class)) { * foo = any.unpack(Foo.class); * } + * // or ... + * if (any.isSameTypeAs(Foo.getDefaultInstance())) { + * foo = any.unpack(Foo.getDefaultInstance()); + * } * * Example 3: Pack and unpack a message in Python. * diff --git a/src/google/protobuf/any.proto b/src/google/protobuf/any.proto index 5ba0a33705..561da0cb15 100644 --- a/src/google/protobuf/any.proto +++ b/src/google/protobuf/any.proto @@ -63,6 +63,10 @@ option csharp_namespace = "Google.Protobuf.WellKnownTypes"; // if (any.is(Foo.class)) { // foo = any.unpack(Foo.class); // } +// // or ... +// if (any.isSameTypeAs(Foo.getDefaultInstance())) { +// foo = any.unpack(Foo.getDefaultInstance()); +// } // // Example 3: Pack and unpack a message in Python. // diff --git a/src/google/protobuf/compiler/java/message.cc b/src/google/protobuf/compiler/java/message.cc index b0389d1b11..12d4406909 100644 --- a/src/google/protobuf/compiler/java/message.cc +++ b/src/google/protobuf/compiler/java/message.cc @@ -1372,12 +1372,14 @@ void ImmutableMessageGenerator::GenerateTopLevelKotlinMembers( GenerateKotlinOrNull(printer); } -void ImmutableMessageGenerator::GenerateKotlinOrNull(io::Printer* printer) const { +void ImmutableMessageGenerator::GenerateKotlinOrNull( + io::Printer* printer) const { for (int i = 0; i < descriptor_->field_count(); i++) { const FieldDescriptor* field = descriptor_->field(i); if (field->has_presence() && GetJavaType(field) == JAVATYPE_MESSAGE) { printer->Print( - "public val $full_classname$OrBuilder.$camelcase_name$OrNull: $full_name$?\n" + "public val $full_classname$OrBuilder.$camelcase_name$OrNull: " + "$full_name$?\n" " get() = if (has$name$()) get$name$() else null\n\n", "full_classname", EscapeKotlinKeywords(name_resolver_->GetClassName(descriptor_, true)), @@ -1592,6 +1594,11 @@ void ImmutableMessageGenerator::GenerateAnyMethods(io::Printer* printer) { " defaultInstance.getDescriptorForType().getFullName());\n" "}\n" "\n" + "public boolean isSameTypeAs(com.google.protobuf.Message message) {\n" + " return getTypeNameFromTypeUrl(getTypeUrl()).equals(\n" + " message.getDescriptorForType().getFullName());\n" + "}\n" + "\n" "@SuppressWarnings(\"serial\")\n" "private volatile com.google.protobuf.Message cachedUnpackValue;\n" "\n" @@ -1617,7 +1624,30 @@ void ImmutableMessageGenerator::GenerateAnyMethods(io::Printer* printer) { " .parseFrom(getValue());\n" " cachedUnpackValue = result;\n" " return result;\n" - "}\n"); + "}\n" + "\n" + "@java.lang.SuppressWarnings(\"unchecked\")\n" + "public T unpackSameTypeAs(" + "T message)\n" + " throws com.google.protobuf.InvalidProtocolBufferException {\n"); + printer->Print( + " boolean invalidValue = false;\n" + " if (cachedUnpackValue != null) {\n" + " if (cachedUnpackValue.getClass() == message.getClass()) {\n" + " return (T) cachedUnpackValue;\n" + " }\n" + " invalidValue = true;\n" + " }\n" + " if (invalidValue || !isSameTypeAs(message)) {\n" + " throw new com.google.protobuf.InvalidProtocolBufferException(\n" + " \"Type of the Any message does not match the given " + "exemplar.\");\n" + " }\n" + " T result = (T) message.getParserForType().parseFrom(getValue());\n" + " cachedUnpackValue = result;\n" + " return result;\n" + "}\n" + "\n"); } } // namespace java