From 3ee67038e52a1398bdb5dd36304482f5615079e9 Mon Sep 17 00:00:00 2001 From: Protobuf Team Bot Date: Thu, 20 Jul 2023 12:46:55 -0700 Subject: [PATCH] Add MapFieldBuilder to support a later change to codegen. PiperOrigin-RevId: 549711661 --- .../google/protobuf/GeneratedMessageV3.java | 37 ++- .../java/com/google/protobuf/MapField.java | 7 +- .../com/google/protobuf/MapFieldBuilder.java | 220 ++++++++++++++++++ .../protobuf/MapFieldReflectionAccessor.java | 48 ++++ 4 files changed, 302 insertions(+), 10 deletions(-) create mode 100644 java/core/src/main/java/com/google/protobuf/MapFieldBuilder.java create mode 100644 java/core/src/main/java/com/google/protobuf/MapFieldReflectionAccessor.java 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 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(); +}