All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.firebase.database.snapshot.RangeMerge Maven / Gradle / Ivy

/*
 * Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.firebase.database.snapshot;

import com.google.firebase.database.core.Path;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Applies a merge of a snap for a given interval of paths. Each leaf in the current node which the
 * relative path lies *after* optExclusiveStart and lies *before or at* optInclusiveEnd will be
 * deleted. Each leaf in snap that lies in the interval will be added to the resulting node. Nods
 * outside of the range are ignored. null for start and end are sentinel values that represent
 * -infinity and infinity respectively (aka includes any path). Priorities of children nodes are
 * treated as leaf children of that node.
 */
public class RangeMerge {

  private final Path optExclusiveStart;
  private final Path optInclusiveEnd;
  private final Node snap;

  public RangeMerge(Path optExclusiveStart, Path optInclusiveEnd, Node snap) {
    this.optExclusiveStart = optExclusiveStart;
    this.optInclusiveEnd = optInclusiveEnd;
    this.snap = snap;
  }

  public RangeMerge(com.google.firebase.database.connection.RangeMerge rangeMerge) {
    List optStartPathList = rangeMerge.getOptExclusiveStart();
    this.optExclusiveStart = (optStartPathList != null) ? new Path(optStartPathList) : null;
    List optEndPathList = rangeMerge.getOptInclusiveEnd();
    this.optInclusiveEnd = (optEndPathList != null) ? new Path(optEndPathList) : null;
    this.snap = NodeUtilities.NodeFromJSON(rangeMerge.getSnap());
  }

  public Node applyTo(Node node) {
    return updateRangeInNode(Path.getEmptyPath(), node, this.snap);
  }

  Path getStart() {
    return optExclusiveStart;
  }

  Path getEnd() {
    return optInclusiveEnd;
  }

  private Node updateRangeInNode(Path currentPath, Node node, Node updateNode) {
    int startComparison =
        (optExclusiveStart == null) ? 1 : currentPath.compareTo(optExclusiveStart);
    int endComparison = (optInclusiveEnd == null) ? -1 : currentPath.compareTo(optInclusiveEnd);
    boolean startInNode = optExclusiveStart != null && currentPath.contains(optExclusiveStart);
    boolean endInNode = optInclusiveEnd != null && currentPath.contains(optInclusiveEnd);
    if (startComparison > 0 && endComparison < 0 && !endInNode) {
      // child is completely contained
      return updateNode;
    } else if (startComparison > 0 && endInNode && updateNode.isLeafNode()) {
      return updateNode;
    } else if (startComparison > 0 && endComparison == 0) {
      assert endInNode;
      assert !updateNode.isLeafNode();
      if (node.isLeafNode()) {
        // Update node was not a leaf node, so we can delete it
        return EmptyNode.Empty();
      } else {
        // Unaffected by range, ignore
        return node;
      }
    } else if (startInNode || endInNode) {
      // There is a partial update we need to do
      // Collect all relevant children
      Set allChildren = new HashSet<>();
      for (NamedNode child : node) {
        allChildren.add(child.getName());
      }
      for (NamedNode child : updateNode) {
        allChildren.add(child.getName());
      }
      List inOrder = new ArrayList<>(allChildren.size() + 1);
      inOrder.addAll(allChildren);
      // Add priority last, so the node is not empty when applying
      if (!updateNode.getPriority().isEmpty() || !node.getPriority().isEmpty()) {
        inOrder.add(ChildKey.getPriorityKey());
      }
      Node newNode = node;
      for (ChildKey key : inOrder) {
        Node currentChild = node.getImmediateChild(key);
        Node updatedChild =
            updateRangeInNode(
                currentPath.child(key),
                node.getImmediateChild(key),
                updateNode.getImmediateChild(key));
        // Only need to update if the node changed
        if (updatedChild != currentChild) {
          newNode = newNode.updateImmediateChild(key, updatedChild);
        }
      }
      return newNode;
    } else {
      // Unaffected by this range
      assert endComparison > 0 || startComparison <= 0;
      return node;
    }
  }

  @Override
  public String toString() {
    return "RangeMerge{"
        + "optExclusiveStart="
        + optExclusiveStart
        + ", optInclusiveEnd="
        + optInclusiveEnd
        + ", snap="
        + snap
        + '}';
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy