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
+ '}';
}
}