Skip to content

Commit

Permalink
Add exemplar variants of the Java Any.is() and Any.unpack() methods.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
protobuf-github-bot authored and copybara-github committed Nov 7, 2022
1 parent ab3dbe5 commit 60b7149
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 4 deletions.
96 changes: 95 additions & 1 deletion java/core/src/test/java/com/google/protobuf/AnyTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
/** Unit tests for Any message. */
@RunWith(JUnit4.class)
public class AnyTest {

@Test
public void testAnyGeneratedApi() throws Exception {
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
Expand Down Expand Up @@ -78,6 +77,40 @@ public void testAnyGeneratedApi() throws Exception {
}
}

@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();
Expand All @@ -90,7 +123,9 @@ public void testCustomTypeUrls() throws Exception {
.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);
Expand All @@ -101,7 +136,9 @@ public void testCustomTypeUrls() throws Exception {
.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);
Expand All @@ -112,12 +149,54 @@ public void testCustomTypeUrls() throws Exception {
.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();
Expand All @@ -132,4 +211,19 @@ public void testCachedUnpackResult() throws Exception {
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();
}
}
4 changes: 4 additions & 0 deletions objectivec/GPBAny.pbobjc.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions src/google/protobuf/any.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//
Expand Down
36 changes: 33 additions & 3 deletions src/google/protobuf/compiler/java/message.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down Expand Up @@ -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"
Expand All @@ -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 extends com.google.protobuf.Message> 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
Expand Down

0 comments on commit 60b7149

Please sign in to comment.