diff --git a/java/core/src/main/java/com/google/protobuf/GeneratedMessageV3.java b/java/core/src/main/java/com/google/protobuf/GeneratedMessageV3.java
index cbad3ca01f..8fd8dd7f6e 100644
--- a/java/core/src/main/java/com/google/protobuf/GeneratedMessageV3.java
+++ b/java/core/src/main/java/com/google/protobuf/GeneratedMessageV3.java
@@ -959,13 +959,27 @@ public abstract class GeneratedMessageV3 extends AbstractMessage implements Seri
* map field directly and thus enables us to access the map field as a list.
*/
@SuppressWarnings({"unused", "rawtypes"})
+ protected MapFieldReflectionAccessor internalGetMapFieldReflection(int fieldNumber) {
+ return internalGetMapField(fieldNumber);
+ }
+
+ /** TODO(b/258340024): Remove, exists for compatibility with generated code. */
+ @Deprecated
+ @SuppressWarnings({"unused", "rawtypes"})
protected MapField internalGetMapField(int fieldNumber) {
// Note that we can't use descriptor names here because this method will
// be called when descriptor is being initialized.
throw new IllegalArgumentException("No map fields found in " + getClass().getName());
}
- /** Like {@link #internalGetMapField} but return a mutable version. */
+ /** Like {@link #internalGetMapFieldReflection} but return a mutable version. */
+ @SuppressWarnings({"unused", "rawtypes"})
+ protected MapFieldReflectionAccessor internalGetMutableMapFieldReflection(int fieldNumber) {
+ return internalGetMutableMapField(fieldNumber);
+ }
+
+ /** TODO(b/258340024): Remove, exists for compatibility with generated code. */
+ @Deprecated
@SuppressWarnings({"unused", "rawtypes"})
protected MapField internalGetMutableMapField(int fieldNumber) {
// Note that we can't use descriptor names here because this method will
@@ -2047,6 +2061,13 @@ public abstract class GeneratedMessageV3 extends AbstractMessage implements Seri
* generated API only allows us to access it as a map. This method returns the underlying map
* field directly and thus enables us to access the map field as a list.
*/
+ @SuppressWarnings("unused")
+ protected MapFieldReflectionAccessor internalGetMapFieldReflection(int fieldNumber) {
+ return internalGetMapField(fieldNumber);
+ }
+
+ /** TODO(b/258340024): Remove, exists for compatibility with generated code. */
+ @Deprecated
@SuppressWarnings({"rawtypes", "unused"})
protected MapField internalGetMapField(int fieldNumber) {
// Note that we can't use descriptor names here because this method will
@@ -2802,7 +2823,7 @@ public abstract class GeneratedMessageV3 extends AbstractMessage implements Seri
final FieldDescriptor descriptor, final Class extends GeneratedMessageV3> messageClass) {
field = descriptor;
Method getDefaultInstanceMethod = getMethodOrDie(messageClass, "getDefaultInstance");
- MapField, ?> defaultMapField =
+ MapFieldReflectionAccessor defaultMapField =
getMapField((GeneratedMessageV3) invokeOrDie(getDefaultInstanceMethod, null));
mapEntryMessageDefaultInstance = defaultMapField.getMapEntryMessageDefaultInstance();
}
@@ -2810,16 +2831,16 @@ public abstract class GeneratedMessageV3 extends AbstractMessage implements Seri
private final FieldDescriptor field;
private final Message mapEntryMessageDefaultInstance;
- private MapField, ?> getMapField(GeneratedMessageV3 message) {
- return (MapField, ?>) message.internalGetMapField(field.getNumber());
+ private MapFieldReflectionAccessor getMapField(GeneratedMessageV3 message) {
+ return message.internalGetMapFieldReflection(field.getNumber());
}
- private MapField, ?> getMapField(GeneratedMessageV3.Builder> builder) {
- return (MapField, ?>) builder.internalGetMapField(field.getNumber());
+ private MapFieldReflectionAccessor getMapField(GeneratedMessageV3.Builder> builder) {
+ return builder.internalGetMapFieldReflection(field.getNumber());
}
- private MapField, ?> getMutableMapField(GeneratedMessageV3.Builder> builder) {
- return (MapField, ?>) builder.internalGetMutableMapField(field.getNumber());
+ private MapFieldReflectionAccessor getMutableMapField(GeneratedMessageV3.Builder> builder) {
+ return builder.internalGetMutableMapFieldReflection(field.getNumber());
}
private Message coerceType(Message value) {
diff --git a/java/core/src/main/java/com/google/protobuf/MapField.java b/java/core/src/main/java/com/google/protobuf/MapField.java
index ee9c5ad39f..1b44181886 100644
--- a/java/core/src/main/java/com/google/protobuf/MapField.java
+++ b/java/core/src/main/java/com/google/protobuf/MapField.java
@@ -53,7 +53,7 @@ import java.util.Set;
*
THREAD-SAFETY NOTE: Read-only access is thread-safe. Users can call getMap() and getList()
* concurrently in multiple threads. If write-access is needed, all access must be synchronized.
*/
-public class MapField implements MutabilityOracle {
+public class MapField extends MapFieldReflectionAccessor implements MutabilityOracle {
/**
* Indicates where the data of this map field is currently stored.
@@ -225,6 +225,7 @@ public class MapField implements MutabilityOracle {
}
/** Gets the content of this MapField as a read-only List. */
+ @Override
List getList() {
if (mode == StorageMode.MAP) {
synchronized (this) {
@@ -238,6 +239,7 @@ public class MapField implements MutabilityOracle {
}
/** Gets a mutable List view of this MapField. */
+ @Override
List getMutableList() {
if (mode != StorageMode.LIST) {
if (mode == StorageMode.MAP) {
@@ -250,6 +252,7 @@ public class MapField implements MutabilityOracle {
}
/** Gets the default instance of the message stored in the list view of this map field. */
+ @Override
Message getMapEntryMessageDefaultInstance() {
return converter.getMessageDefaultInstance();
}
@@ -278,7 +281,7 @@ public class MapField implements MutabilityOracle {
}
/** An internal map that checks for mutability before delegating. */
- private static class MutabilityAwareMap implements Map {
+ static class MutabilityAwareMap implements Map {
private final MutabilityOracle mutabilityOracle;
private final Map delegate;
diff --git a/java/core/src/main/java/com/google/protobuf/MapFieldBuilder.java b/java/core/src/main/java/com/google/protobuf/MapFieldBuilder.java
new file mode 100644
index 0000000000..84ac1b0841
--- /dev/null
+++ b/java/core/src/main/java/com/google/protobuf/MapFieldBuilder.java
@@ -0,0 +1,220 @@
+// 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.
+
+package com.google.protobuf;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+
+/**
+ * Internal representation of map fields in generated builders.
+ *
+ * This class supports accessing the map field as a {@link Map} to be used in generated API and
+ * also supports accessing the field as a {@link List} to be used in reflection API. It keeps track
+ * of where the data is currently stored and do necessary conversions between map and list.
+ *
+ *
This class is a protobuf implementation detail. Users shouldn't use this class directly.
+ */
+public class MapFieldBuilder<
+ KeyT,
+ MessageOrBuilderT extends MessageOrBuilder,
+ MessageT extends MessageOrBuilderT,
+ BuilderT extends MessageOrBuilderT>
+ extends MapFieldReflectionAccessor {
+
+ // Only one of the three fields may be non-null at any time.
+ /** nullable */
+ Map builderMap = new LinkedHashMap<>();
+
+ /** nullable */
+ Map messageMap = null;
+
+ // messageList elements are always MapEntry, but we need a List for
+ // reflection.
+ /** nullable */
+ List messageList = null;
+
+ Converter converter;
+
+ /** Convert a MessageOrBuilder to a Message regardless of which it holds. */
+ public interface Converter<
+ KeyT, MessageOrBuilderT extends MessageOrBuilder, MessageT extends MessageOrBuilderT> {
+ MessageT build(MessageOrBuilderT val);
+
+ MapEntry defaultEntry();
+ }
+
+ public MapFieldBuilder(Converter converter) {
+ this.converter = converter;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void forEachListEntry(BiConsumer f) {
+ messageList.forEach(
+ entry -> {
+ MapEntry typedEntry = (MapEntry) entry;
+ f.accept(typedEntry.getKey(), typedEntry.getValue());
+ });
+ }
+
+ public Map ensureBuilderMap() {
+ if (builderMap != null) {
+ return builderMap;
+ }
+ if (messageMap != null) {
+ builderMap = new LinkedHashMap<>(messageMap.size());
+ messageMap.forEach((key, value) -> builderMap.put(key, value));
+ messageMap = null;
+ return builderMap;
+ }
+ builderMap = new LinkedHashMap<>(messageList.size());
+ forEachListEntry((key, value) -> builderMap.put(key, value));
+ messageList = null;
+ return builderMap;
+ }
+
+ public List ensureMessageList() {
+ if (messageList != null) {
+ return messageList;
+ }
+ if (builderMap != null) {
+ messageList = new ArrayList<>(builderMap.size());
+ builderMap.forEach(
+ (key, value) ->
+ messageList.add(
+ converter.defaultEntry().toBuilder()
+ .setKey(key)
+ .setValue(converter.build(value))
+ .build()));
+ builderMap = null;
+ return messageList;
+ }
+ messageList = new ArrayList<>(messageMap.size());
+ messageMap.forEach(
+ (key, value) ->
+ messageList.add(
+ converter.defaultEntry().toBuilder().setKey(key).setValue(value).build()));
+ messageMap = null;
+ return messageList;
+ }
+
+ public Map ensureMessageMap() {
+ messageMap = populateMutableMap();
+ builderMap = null;
+ messageList = null;
+ return messageMap;
+ }
+
+ public Map getImmutableMap() {
+ return new MapField.MutabilityAwareMap<>(MutabilityOracle.IMMUTABLE, populateMutableMap());
+ }
+
+ private Map populateMutableMap() {
+ if (messageMap != null) {
+ return messageMap;
+ }
+ if (builderMap != null) {
+ Map toReturn = new LinkedHashMap<>(builderMap.size());
+ builderMap.forEach((key, value) -> toReturn.put(key, converter.build(value)));
+ return toReturn;
+ }
+ Map toReturn = new LinkedHashMap<>(messageList.size());
+ forEachListEntry((key, value) -> toReturn.put(key, value));
+ return toReturn;
+ }
+
+ public void mergeFrom(MapField other) {
+ ensureBuilderMap().putAll(MapFieldLite.copy(other.getMap()));
+ }
+
+ public void clear() {
+ builderMap = new LinkedHashMap<>();
+ messageMap = null;
+ messageList = null;
+ }
+
+ private boolean typedEquals(MapFieldBuilder other) {
+ return MapFieldLite.equals(
+ ensureBuilderMap(), other.ensureBuilderMap());
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean equals(Object object) {
+ if (!(object instanceof MapFieldBuilder)) {
+ return false;
+ }
+ return typedEquals((MapFieldBuilder) object);
+ }
+
+ @Override
+ public int hashCode() {
+ return MapFieldLite.calculateHashCodeForMap(ensureBuilderMap());
+ }
+
+ /** Returns a deep copy of this MapFieldBuilder. */
+ public MapFieldBuilder copy() {
+ MapFieldBuilder clone =
+ new MapFieldBuilder<>(converter);
+ clone.ensureBuilderMap().putAll(ensureBuilderMap());
+ return clone;
+ }
+
+ /** Converts this MapFieldBuilder to a MapField. */
+ public MapField build(MapEntry defaultEntry) {
+ MapField mapField = MapField.newMapField(defaultEntry);
+ Map map = mapField.getMutableMap();
+ ensureBuilderMap().forEach((key, value) -> map.put(key, converter.build(value)));
+ mapField.makeImmutable();
+ return mapField;
+ }
+
+ // MapFieldReflectionAccessor implementation.
+ /** Gets the content of this MapField as a read-only List. */
+ @Override
+ List getList() {
+ return ensureMessageList();
+ }
+
+ /** Gets a mutable List view of this MapField. */
+ @Override
+ List getMutableList() {
+ return ensureMessageList();
+ }
+
+ /** Gets the default instance of the message stored in the list view of this map field. */
+ @Override
+ Message getMapEntryMessageDefaultInstance() {
+ return converter.defaultEntry();
+ }
+}
diff --git a/java/core/src/main/java/com/google/protobuf/MapFieldReflectionAccessor.java b/java/core/src/main/java/com/google/protobuf/MapFieldReflectionAccessor.java
new file mode 100644
index 0000000000..ed2c09ada4
--- /dev/null
+++ b/java/core/src/main/java/com/google/protobuf/MapFieldReflectionAccessor.java
@@ -0,0 +1,48 @@
+// 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.
+
+package com.google.protobuf;
+
+import java.util.List;
+
+/**
+ * A base class for package private shared methods between MapField and MapFieldBuilder to allow
+ * reflection to access both.
+ */
+public abstract class MapFieldReflectionAccessor {
+ /** Gets the content of this MapField as a read-only List. */
+ abstract List getList();
+
+ /** Gets a mutable List view of this MapField. */
+ abstract List getMutableList();
+
+ /** Gets the default instance of the message stored in the list view of this map field. */
+ abstract Message getMapEntryMessageDefaultInstance();
+}