2828
2929/** A structured object value stored in Firestore. */
3030public final class ObjectValue implements Cloneable {
31+ private final Object lock = new Object (); // Monitor object
32+
3133 /**
3234 * The immutable Value proto for this object. Local mutations are stored in `overlayMap` and only
3335 * applied when {@link #buildProto()} is invoked.
@@ -124,11 +126,13 @@ private Value extractNestedValue(Value value, FieldPath fieldPath) {
124126 * invocations are based on this memoized result.
125127 */
126128 private Value buildProto () {
127- synchronized (overlayMap ) {
128- MapValue mergedResult = applyOverlay (FieldPath .EMPTY_PATH , overlayMap );
129- if (mergedResult != null ) {
130- partialValue = Value .newBuilder ().setMapValue (mergedResult ).build ();
131- overlayMap .clear ();
129+ synchronized (lock ) {
130+ if (!overlayMap .isEmpty ()) {
131+ MapValue mergedResult = applyOverlayLocked (FieldPath .EMPTY_PATH , overlayMap );
132+ if (mergedResult != null ) {
133+ partialValue = Value .newBuilder ().setMapValue (mergedResult ).build ();
134+ overlayMap .clear ();
135+ }
132136 }
133137 }
134138 return partialValue ;
@@ -171,31 +175,33 @@ public void setAll(Map<FieldPath, Value> data) {
171175 * Adds {@code value} to the overlay map at {@code path}. Creates nested map entries if needed.
172176 */
173177 private void setOverlay (FieldPath path , @ Nullable Value value ) {
174- Map <String , Object > currentLevel = overlayMap ;
178+ synchronized (lock ) {
179+ Map <String , Object > currentLevel = overlayMap ;
175180
176- for (int i = 0 ; i < path .length () - 1 ; ++i ) {
177- String currentSegment = path .getSegment (i );
178- Object currentValue = currentLevel .get (currentSegment );
181+ for (int i = 0 ; i < path .length () - 1 ; ++i ) {
182+ String currentSegment = path .getSegment (i );
183+ Object currentValue = currentLevel .get (currentSegment );
179184
180- if (currentValue instanceof Map ) {
181- // Re-use a previously created map
182- currentLevel = (Map <String , Object >) currentValue ;
183- } else if (currentValue instanceof Value
184- && ((Value ) currentValue ).getValueTypeCase () == Value .ValueTypeCase .MAP_VALUE ) {
185- // Convert the existing Protobuf MapValue into a Java map
186- Map <String , Object > nextLevel =
187- new HashMap <>(((Value ) currentValue ).getMapValue ().getFieldsMap ());
188- currentLevel .put (currentSegment , nextLevel );
189- currentLevel = nextLevel ;
190- } else {
191- // Create an empty hash map to represent the current nesting level
192- Map <String , Object > nextLevel = new HashMap <>();
193- currentLevel .put (currentSegment , nextLevel );
194- currentLevel = nextLevel ;
185+ if (currentValue instanceof Map ) {
186+ // Re-use a previously created map
187+ currentLevel = (Map <String , Object >) currentValue ;
188+ } else if (currentValue instanceof Value
189+ && ((Value ) currentValue ).getValueTypeCase () == Value .ValueTypeCase .MAP_VALUE ) {
190+ // Convert the existing Protobuf MapValue into a Java map
191+ Map <String , Object > nextLevel =
192+ new HashMap <>(((Value ) currentValue ).getMapValue ().getFieldsMap ());
193+ currentLevel .put (currentSegment , nextLevel );
194+ currentLevel = nextLevel ;
195+ } else {
196+ // Create an empty hash map to represent the current nesting level
197+ Map <String , Object > nextLevel = new HashMap <>();
198+ currentLevel .put (currentSegment , nextLevel );
199+ currentLevel = nextLevel ;
200+ }
195201 }
196- }
197202
198- currentLevel .put (path .getLastSegment (), value );
203+ currentLevel .put (path .getLastSegment (), value );
204+ }
199205 }
200206
201207 /**
@@ -208,7 +214,7 @@ private void setOverlay(FieldPath path, @Nullable Value value) {
208214 * overlayMap}.
209215 * @return The merged data at `currentPath` or null if no modifications were applied.
210216 */
211- private @ Nullable MapValue applyOverlay (
217+ private @ Nullable MapValue applyOverlayLocked (
212218 FieldPath currentPath , Map <String , Object > currentOverlays ) {
213219 boolean modified = false ;
214220
@@ -227,7 +233,7 @@ private void setOverlay(FieldPath path, @Nullable Value value) {
227233 if (value instanceof Map ) {
228234 @ Nullable
229235 MapValue nested =
230- applyOverlay (currentPath .append (pathSegment ), (Map <String , Object >) value );
236+ applyOverlayLocked (currentPath .append (pathSegment ), (Map <String , Object >) value );
231237 if (nested != null ) {
232238 resultAtPath .putFields (pathSegment , Value .newBuilder ().setMapValue (nested ).build ());
233239 modified = true ;
0 commit comments