Skip to content

Commit

Permalink
Add MapFieldBuilder and change codegen to generate it and the put{fie…
Browse files Browse the repository at this point in the history
…ld}BuilderIfAbsent method.

PiperOrigin-RevId: 563738402
  • Loading branch information
protobuf-github-bot authored and copybara-github committed Sep 8, 2023
1 parent de87585 commit 955d4ab
Show file tree
Hide file tree
Showing 7 changed files with 391 additions and 125 deletions.
15 changes: 12 additions & 3 deletions java/core/src/main/java/com/google/protobuf/MapFieldBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@ public class MapFieldBuilder<
/** nullable */
Map<KeyT, MessageT> messageMap = null;

// messageList elements are always MapEntry<KeyT, MessageT>, but we need a List<Message> for
// reflection.
// We need a List<Message> for reflection.
//
// messageList elements are always MapEntry<KeyT, SomeT extends Message>, where SomeT and MessageT
// have the same descriptor (i.e. SomeT can be DynamicMessage)
/** nullable */
List<Message> messageList = null;

Expand All @@ -80,8 +82,15 @@ public MapFieldBuilder(Converter<KeyT, MessageOrBuilderT, MessageT> converter) {
@SuppressWarnings("unchecked")
private List<MapEntry<KeyT, MessageT>> getMapEntryList() {
ArrayList<MapEntry<KeyT, MessageT>> list = new ArrayList<>(messageList.size());
Class<?> valueClass = converter.defaultEntry().getValue().getClass();
for (Message entry : messageList) {
list.add((MapEntry<KeyT, MessageT>) entry);
MapEntry<KeyT, ?> typedEntry = (MapEntry<KeyT, ?>) entry;
if (valueClass.isInstance(typedEntry.getValue())) {
list.add((MapEntry<KeyT, MessageT>) typedEntry);
} else {
// This needs to use mergeFrom to allow MapEntry<KeyT, DynamicMessage> to be used.
list.add(converter.defaultEntry().toBuilder().mergeFrom(entry).build());
}
}
return list;
}
Expand Down
21 changes: 9 additions & 12 deletions java/core/src/test/java/com/google/protobuf/MapForProto2Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ public void testMutableMapLifecycle() {
assertThat(builder.build().getInt32ToInt32Field()).isEqualTo(newMap(1, 2));
try {
intMap.put(2, 3);
assertWithMessage("expected exception").fail();
assertWithMessage("expected exception intMap").fail();
} catch (UnsupportedOperationException e) {
// expected
}
Expand All @@ -347,7 +347,7 @@ public void testMutableMapLifecycle() {
.isEqualTo(newMap(1, TestMap.EnumValue.BAR));
try {
enumMap.put(2, TestMap.EnumValue.FOO);
assertWithMessage("expected exception").fail();
assertWithMessage("expected exception enumMap").fail();
} catch (UnsupportedOperationException e) {
// expected
}
Expand All @@ -361,27 +361,24 @@ public void testMutableMapLifecycle() {
assertThat(builder.build().getInt32ToStringField()).isEqualTo(newMap(1, "1"));
try {
stringMap.put(2, "2");
assertWithMessage("expected exception").fail();
assertWithMessage("expected exception stringMap").fail();
} catch (UnsupportedOperationException e) {
// expected
}
assertThat(builder.getInt32ToStringField()).isEqualTo(newMap(1, "1"));
builder.getMutableInt32ToStringField().put(2, "2");
assertThat(builder.getInt32ToStringField()).isEqualTo(newMap(1, "1", 2, "2"));

// Message maps are handled differently, and don't freeze old mutable collections.
Map<Integer, TestMap.MessageValue> messageMap = builder.getMutableInt32ToMessageField();
messageMap.put(1, TestMap.MessageValue.getDefaultInstance());
assertThat(builder.build().getInt32ToMessageField())
.isEqualTo(newMap(1, TestMap.MessageValue.getDefaultInstance()));
try {
messageMap.put(2, TestMap.MessageValue.getDefaultInstance());
assertWithMessage("expected exception").fail();
} catch (UnsupportedOperationException e) {
// expected
}
assertThat(builder.getInt32ToMessageField())
.isEqualTo(newMap(1, TestMap.MessageValue.getDefaultInstance()));
builder.getMutableInt32ToMessageField().put(2, TestMap.MessageValue.getDefaultInstance());
// Mutations on old mutable maps don't affect the builder state.
messageMap.put(2, TestMap.MessageValue.getDefaultInstance());
assertThat(builder.getInt32ToMessageField()).isEqualTo(
newMap(1, TestMap.MessageValue.getDefaultInstance()));
builder.putInt32ToMessageField(2, TestMap.MessageValue.getDefaultInstance());
assertThat(builder.getInt32ToMessageField()).isEqualTo(
newMap(1, TestMap.MessageValue.getDefaultInstance(),
2, TestMap.MessageValue.getDefaultInstance()));
Expand Down
21 changes: 9 additions & 12 deletions java/core/src/test/java/com/google/protobuf/MapTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ public void testMutableMapLifecycle() {
assertThat(builder.build().getInt32ToInt32Field()).isEqualTo(newMap(1, 2));
try {
intMap.put(2, 3);
assertWithMessage("expected exception").fail();
assertWithMessage("expected exception intMap").fail();
} catch (UnsupportedOperationException e) {
// expected
}
Expand All @@ -346,7 +346,7 @@ public void testMutableMapLifecycle() {
.isEqualTo(newMap(1, TestMap.EnumValue.BAR));
try {
enumMap.put(2, TestMap.EnumValue.FOO);
assertWithMessage("expected exception").fail();
assertWithMessage("expected exception enumMap").fail();
} catch (UnsupportedOperationException e) {
// expected
}
Expand All @@ -360,26 +360,23 @@ public void testMutableMapLifecycle() {
assertThat(builder.build().getInt32ToStringField()).isEqualTo(newMap(1, "1"));
try {
stringMap.put(2, "2");
assertWithMessage("expected exception").fail();
assertWithMessage("expected exception stringMap").fail();
} catch (UnsupportedOperationException e) {
// expected
}
assertThat(builder.getInt32ToStringField()).isEqualTo(newMap(1, "1"));
builder.putInt32ToStringField(2, "2");
assertThat(builder.getInt32ToStringField()).isEqualTo(newMap(1, "1", 2, "2"));

// Message maps are handled differently, and don't freeze old mutable collections.
Map<Integer, TestMap.MessageValue> messageMap = builder.getMutableInt32ToMessageField();
messageMap.put(1, TestMap.MessageValue.getDefaultInstance());
assertThat( builder.build().getInt32ToMessageField())
.isEqualTo(newMap(1, TestMap.MessageValue.getDefaultInstance()));
try {
messageMap.put(2, TestMap.MessageValue.getDefaultInstance());
assertWithMessage("expected exception").fail();
} catch (UnsupportedOperationException e) {
// expected
}
assertThat(builder.getInt32ToMessageField())
assertThat(builder.build().getInt32ToMessageField())
.isEqualTo(newMap(1, TestMap.MessageValue.getDefaultInstance()));
// Mutations on old mutable maps don't affect the builder state.
messageMap.put(2, TestMap.MessageValue.getDefaultInstance());
assertThat(builder.getInt32ToMessageField()).isEqualTo(
newMap(1, TestMap.MessageValue.getDefaultInstance()));
builder.putInt32ToMessageField(2, TestMap.MessageValue.getDefaultInstance());
assertThat(builder.getInt32ToMessageField()).isEqualTo(
newMap(1, TestMap.MessageValue.getDefaultInstance(),
Expand Down
Loading

0 comments on commit 955d4ab

Please sign in to comment.