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