Skip to content

Commit a946e64

Browse files
dconeybejeremyjiang-dev
authored andcommitted
Change DocumentOverlayCache.saveOverlays() to require non-null values in the given Map (#3518)
1 parent 2dcd7d2 commit a946e64

File tree

7 files changed

+57
-12
lines changed

7 files changed

+57
-12
lines changed

firebase-firestore/src/main/java/com/google/firebase/firestore/local/LocalDocumentsView.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,10 @@ private void recalculateAndSaveOverlays(Map<DocumentKey, MutableDocument> docs)
202202
Map<DocumentKey, Mutation> overlays = new HashMap<>();
203203
for (DocumentKey key : entry.getValue()) {
204204
if (!processed.contains(key)) {
205-
overlays.put(key, Mutation.calculateOverlayMutation(docs.get(key), masks.get(key)));
205+
Mutation mutation = Mutation.calculateOverlayMutation(docs.get(key), masks.get(key));
206+
if (mutation != null) {
207+
overlays.put(key, mutation);
208+
}
206209
processed.add(key);
207210
}
208211
}

firebase-firestore/src/main/java/com/google/firebase/firestore/local/MemoryDocumentOverlayCache.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
package com.google.firebase.firestore.local;
1616

17+
import static com.google.firebase.firestore.util.Preconditions.checkNotNull;
18+
1719
import androidx.annotation.Nullable;
1820
import com.google.firebase.firestore.model.DocumentKey;
1921
import com.google.firebase.firestore.model.ResourcePath;
@@ -50,11 +52,7 @@ public Map<DocumentKey, Overlay> getOverlays(SortedSet<DocumentKey> keys) {
5052
return result;
5153
}
5254

53-
private void saveOverlay(int largestBatchId, @Nullable Mutation mutation) {
54-
if (mutation == null) {
55-
return;
56-
}
57-
55+
private void saveOverlay(int largestBatchId, Mutation mutation) {
5856
// Remove the association of the overlay to its batch id.
5957
Overlay existing = this.overlays.get(mutation.getKey());
6058
if (existing != null) {
@@ -73,7 +71,8 @@ private void saveOverlay(int largestBatchId, @Nullable Mutation mutation) {
7371
@Override
7472
public void saveOverlays(int largestBatchId, Map<DocumentKey, Mutation> overlays) {
7573
for (Map.Entry<DocumentKey, Mutation> entry : overlays.entrySet()) {
76-
saveOverlay(largestBatchId, entry.getValue());
74+
Mutation overlay = checkNotNull(entry.getValue(), "null value for key: %s", entry.getKey());
75+
saveOverlay(largestBatchId, overlay);
7776
}
7877
}
7978

firebase-firestore/src/main/java/com/google/firebase/firestore/local/SQLiteDocumentOverlayCache.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import static com.google.firebase.firestore.util.Assert.fail;
1818
import static com.google.firebase.firestore.util.Assert.hardAssert;
19+
import static com.google.firebase.firestore.util.Preconditions.checkNotNull;
1920

2021
import android.database.Cursor;
2122
import androidx.annotation.Nullable;
@@ -106,7 +107,7 @@ private void processSingleCollection(
106107
}
107108
}
108109

109-
private void saveOverlay(int largestBatchId, DocumentKey key, @Nullable Mutation mutation) {
110+
private void saveOverlay(int largestBatchId, DocumentKey key, Mutation mutation) {
110111
String group = key.getCollectionGroup();
111112
String collectionPath = EncodedPath.encode(key.getPath().popLast());
112113
String documentId = key.getPath().getLastSegment();
@@ -125,9 +126,9 @@ private void saveOverlay(int largestBatchId, DocumentKey key, @Nullable Mutation
125126
@Override
126127
public void saveOverlays(int largestBatchId, Map<DocumentKey, Mutation> overlays) {
127128
for (Map.Entry<DocumentKey, Mutation> entry : overlays.entrySet()) {
128-
if (entry.getValue() != null) {
129-
saveOverlay(largestBatchId, entry.getKey(), entry.getValue());
130-
}
129+
DocumentKey key = entry.getKey();
130+
Mutation overlay = checkNotNull(entry.getValue(), "null value for key: %s", key);
131+
saveOverlay(largestBatchId, key, overlay);
131132
}
132133
}
133134

firebase-firestore/src/main/java/com/google/firebase/firestore/model/mutation/Mutation.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public abstract class Mutation {
8787
* of the document, and a {@link FieldMask} representing the fields that are mutated by the local
8888
* mutations.
8989
*/
90+
@Nullable
9091
public static Mutation calculateOverlayMutation(MutableDocument doc, @Nullable FieldMask mask) {
9192
if ((!doc.hasLocalMutations()) || (mask != null && mask.getMask().isEmpty())) {
9293
return null;

firebase-firestore/src/main/java/com/google/firebase/firestore/model/mutation/MutationBatch.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,9 @@ public Map<DocumentKey, Mutation> applyToLocalDocumentSet(
151151
// a Set(or Delete) mutation, instead of trying to create a patch mutation as the overlay.
152152
mutatedFields = documentsWithoutRemoteVersion.contains(key) ? null : mutatedFields;
153153
Mutation overlay = Mutation.calculateOverlayMutation(document, mutatedFields);
154-
overlays.put(key, overlay);
154+
if (overlay != null) {
155+
overlays.put(key, overlay);
156+
}
155157
if (!document.isValidDocument()) {
156158
document.convertToNoDocument(SnapshotVersion.NONE);
157159
}

firebase-firestore/src/main/java/com/google/firebase/firestore/util/Preconditions.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
package com.google.firebase.firestore.util;
1616

17+
import java.util.Locale;
1718
import javax.annotation.Nonnull;
1819
import javax.annotation.Nullable;
1920

@@ -149,6 +150,24 @@ public static <T extends Object> T checkNotNull(
149150
return reference;
150151
}
151152

153+
/**
154+
* Ensures that an object reference passed as a parameter to the calling method is not null.
155+
*
156+
* @param reference an object reference
157+
* @param errorFormatString the exception message to use if the check fails; will be formatted
158+
* using {@link String#format}.
159+
* @param formatArgs the args to specify when formatting the given {@code errorFormatString}.
160+
* @return the non-null reference that was validated
161+
* @throws NullPointerException if {@code reference} is null
162+
*/
163+
public static <T extends Object> T checkNotNull(
164+
@Nonnull T reference, String errorFormatString, @Nullable Object... formatArgs) {
165+
if (reference == null) {
166+
throw new NullPointerException(String.format(Locale.US, errorFormatString, formatArgs));
167+
}
168+
return reference;
169+
}
170+
152171
/**
153172
* Ensures the truth of an expression involving the state of the calling instance, but not
154173
* involving any parameters to the calling method.

firebase-firestore/src/test/java/com/google/firebase/firestore/local/DocumentOverlayCacheTestCase.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import static com.google.firebase.firestore.testutil.TestUtil.setMutation;
2424
import static org.junit.Assert.assertEquals;
2525
import static org.junit.Assert.assertNull;
26+
import static org.junit.Assert.assertThrows;
2627
import static org.junit.Assert.assertTrue;
2728

2829
import com.google.firebase.firestore.auth.User;
@@ -41,6 +42,7 @@
4142
import org.junit.After;
4243
import org.junit.Before;
4344
import org.junit.Test;
45+
import org.junit.function.ThrowingRunnable;
4446

4547
/**
4648
* These are tests for any implementation of the DocumentOverlayCache interface.
@@ -252,6 +254,24 @@ public void testUpdateDocumentOverlay() {
252254
assertNull(cache.getOverlay(DocumentKey.fromPathString("coll/doc")));
253255
}
254256

257+
@Test
258+
public void testSaveOverlaysThrowsNullPointerExceptionOnNullMapValue() {
259+
Map<DocumentKey, Mutation> data = new HashMap<>();
260+
data.put(key("coll/doc"), null);
261+
262+
NullPointerException e =
263+
assertThrows(
264+
NullPointerException.class,
265+
new ThrowingRunnable() {
266+
@Override
267+
public void run() {
268+
cache.saveOverlays(1, data);
269+
}
270+
});
271+
272+
assertThat(e.getMessage()).contains("coll/doc");
273+
}
274+
255275
void verifyOverlayContains(Map<DocumentKey, Overlay> overlays, String... keys) {
256276
Set<DocumentKey> expected = Arrays.stream(keys).map(TestUtil::key).collect(Collectors.toSet());
257277
assertThat(overlays.keySet()).containsExactlyElementsIn(expected);

0 commit comments

Comments
 (0)