public DocumentChanges computeDocChanges()

in firebase-firestore/src/main/java/com/google/firebase/firestore/core/View.java [129:243]


  public DocumentChanges computeDocChanges(
      ImmutableSortedMap<DocumentKey, Document> docChanges,
      @Nullable DocumentChanges previousChanges) {
    DocumentViewChangeSet changeSet =
        previousChanges != null ? previousChanges.changeSet : new DocumentViewChangeSet();
    DocumentSet oldDocumentSet =
        previousChanges != null ? previousChanges.documentSet : documentSet;
    ImmutableSortedSet<DocumentKey> newMutatedKeys =
        previousChanges != null ? previousChanges.mutatedKeys : mutatedKeys;
    DocumentSet newDocumentSet = oldDocumentSet;
    boolean needsRefill = false;

    // Track the last doc in a (full) limit. This is necessary, because some update (a delete, or an
    // update moving a doc past the old limit) might mean there is some other document in the local
    // cache that either should come (1) between the old last limit doc and the new last document,
    // in the case of updates, or (2) after the new last document, in the case of deletes. So we
    // keep this doc at the old limit to compare the updates to.
    //
    // Note that this should never get used in a refill (when previousChanges is set), because there
    // will only be adds -- no deletes or updates.
    Document lastDocInLimit =
        (query.hasLimitToFirst() && oldDocumentSet.size() == query.getLimitToFirst())
            ? oldDocumentSet.getLastDocument()
            : null;
    Document firstDocInLimit =
        (query.hasLimitToLast() && oldDocumentSet.size() == query.getLimitToLast())
            ? oldDocumentSet.getFirstDocument()
            : null;

    for (Map.Entry<DocumentKey, Document> entry : docChanges) {
      DocumentKey key = entry.getKey();
      Document oldDoc = oldDocumentSet.getDocument(key);
      Document newDoc = query.matches(entry.getValue()) ? entry.getValue() : null;

      boolean oldDocHadPendingMutations =
          oldDoc != null && this.mutatedKeys.contains(oldDoc.getKey());

      // We only consider committed mutations for documents that were mutated during the lifetime of
      // the view.
      boolean newDocHasPendingMutations =
          newDoc != null
              && (newDoc.hasLocalMutations()
                  || (this.mutatedKeys.contains(newDoc.getKey())
                      && newDoc.hasCommittedMutations()));

      boolean changeApplied = false;

      // Calculate change
      if (oldDoc != null && newDoc != null) {
        boolean docsEqual = oldDoc.getData().equals(newDoc.getData());
        if (!docsEqual) {
          if (!shouldWaitForSyncedDocument(oldDoc, newDoc)) {
            changeSet.addChange(DocumentViewChange.create(Type.MODIFIED, newDoc));
            changeApplied = true;

            if ((lastDocInLimit != null && query.comparator().compare(newDoc, lastDocInLimit) > 0)
                || (firstDocInLimit != null
                    && query.comparator().compare(newDoc, firstDocInLimit) < 0)) {
              // This doc moved from inside the limit to outside the limit. That means there may be
              // some doc in the local cache that should be included instead.
              needsRefill = true;
            }
          }
        } else if (oldDocHadPendingMutations != newDocHasPendingMutations) {
          changeSet.addChange(DocumentViewChange.create(Type.METADATA, newDoc));
          changeApplied = true;
        }
      } else if (oldDoc == null && newDoc != null) {
        changeSet.addChange(DocumentViewChange.create(Type.ADDED, newDoc));
        changeApplied = true;
      } else if (oldDoc != null && newDoc == null) {
        changeSet.addChange(DocumentViewChange.create(Type.REMOVED, oldDoc));
        changeApplied = true;
        if (lastDocInLimit != null || firstDocInLimit != null) {
          // A doc was removed from a full limit query. We'll need to requery from the local cache
          // to see if we know about some other doc that should be in the results.
          needsRefill = true;
        }
      }

      if (changeApplied) {
        if (newDoc != null) {
          newDocumentSet = newDocumentSet.add(newDoc);
          if (newDoc.hasLocalMutations()) {
            newMutatedKeys = newMutatedKeys.insert(newDoc.getKey());
          } else {
            newMutatedKeys = newMutatedKeys.remove(newDoc.getKey());
          }
        } else {
          newDocumentSet = newDocumentSet.remove(key);
          newMutatedKeys = newMutatedKeys.remove(key);
        }
      }
    }

    // Drop documents out to meet limitToFirst/limitToLast requirement.
    if (query.hasLimitToFirst() || query.hasLimitToLast()) {
      long limit = query.hasLimitToFirst() ? query.getLimitToFirst() : query.getLimitToLast();
      for (long i = newDocumentSet.size() - limit; i > 0; --i) {
        Document oldDoc =
            query.hasLimitToFirst()
                ? newDocumentSet.getLastDocument()
                : newDocumentSet.getFirstDocument();
        newDocumentSet = newDocumentSet.remove(oldDoc.getKey());
        newMutatedKeys = newMutatedKeys.remove(oldDoc.getKey());
        changeSet.addChange(DocumentViewChange.create(Type.REMOVED, oldDoc));
      }
    }

    hardAssert(
        !needsRefill || previousChanges == null,
        "View was refilled using docs that themselves needed refilling.");

    return new DocumentChanges(newDocumentSet, changeSet, newMutatedKeys, needsRefill);
  }