Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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.core;
import com.google.firebase.database.core.utilities.ImmutableTree;
import com.google.firebase.database.snapshot.ChildKey;
import com.google.firebase.database.snapshot.NamedNode;
import com.google.firebase.database.snapshot.Node;
import com.google.firebase.database.snapshot.NodeUtilities;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* This class holds a collection of writes that can be applied to nodes in unison. It abstracts away
* the logic with dealing with priority writes and multiple nested writes. At any given path there
* is only allowed to be one write modifying that path. Any write to an existing path or shadowing
* an existing path will modify that existing write to reflect the write added.
*/
public class CompoundWrite implements Iterable> {
private static final CompoundWrite EMPTY = new CompoundWrite(new ImmutableTree(null));
private final ImmutableTree writeTree;
private CompoundWrite(ImmutableTree writeTree) {
this.writeTree = writeTree;
}
public static CompoundWrite emptyWrite() {
return EMPTY;
}
public static CompoundWrite fromValue(Map merge) {
ImmutableTree writeTree = ImmutableTree.emptyInstance();
for (Map.Entry entry : merge.entrySet()) {
ImmutableTree tree = new ImmutableTree<>(NodeUtilities.NodeFromJSON(entry.getValue()));
writeTree = writeTree.setTree(new Path(entry.getKey()), tree);
}
return new CompoundWrite(writeTree);
}
public static CompoundWrite fromChildMerge(Map merge) {
ImmutableTree writeTree = ImmutableTree.emptyInstance();
for (Map.Entry entry : merge.entrySet()) {
ImmutableTree tree = new ImmutableTree<>(entry.getValue());
writeTree = writeTree.setTree(new Path(entry.getKey()), tree);
}
return new CompoundWrite(writeTree);
}
public static CompoundWrite fromPathMerge(Map merge) {
ImmutableTree writeTree = ImmutableTree.emptyInstance();
for (Map.Entry entry : merge.entrySet()) {
ImmutableTree tree = new ImmutableTree<>(entry.getValue());
writeTree = writeTree.setTree(entry.getKey(), tree);
}
return new CompoundWrite(writeTree);
}
public CompoundWrite addWrite(Path path, Node node) {
if (path.isEmpty()) {
return new CompoundWrite(new ImmutableTree<>(node));
} else {
Path rootMostPath = this.writeTree.findRootMostPathWithValue(path);
if (rootMostPath != null) {
Path relativePath = Path.getRelative(rootMostPath, path);
Node value = this.writeTree.get(rootMostPath);
ChildKey back = relativePath.getBack();
if (back != null
&& back.isPriorityChildName()
&& value.getChild(relativePath.getParent()).isEmpty()) {
// Ignore priority updates on empty nodes
return this;
} else {
value = value.updateChild(relativePath, node);
return new CompoundWrite(this.writeTree.set(rootMostPath, value));
}
} else {
ImmutableTree subtree = new ImmutableTree<>(node);
ImmutableTree newWriteTree = this.writeTree.setTree(path, subtree);
return new CompoundWrite(newWriteTree);
}
}
}
public CompoundWrite addWrite(ChildKey key, Node node) {
return addWrite(new Path(key), node);
}
public CompoundWrite addWrites(final Path path, CompoundWrite updates) {
return updates.writeTree.fold(
this,
new ImmutableTree.TreeVisitor() {
@Override
public CompoundWrite onNodeValue(Path relativePath, Node value, CompoundWrite accum) {
return accum.addWrite(path.child(relativePath), value);
}
});
}
/**
* Will remove a write at the given path and deeper paths. This will not modify a write
* at a higher location, which must be removed by calling this method with that path.
*
* @param path The path at which a write and all deeper writes should be removed
* @return The new WriteCompound with the removed path
*/
public CompoundWrite removeWrite(Path path) {
if (path.isEmpty()) {
return EMPTY;
} else {
ImmutableTree newWriteTree =
writeTree.setTree(path, ImmutableTree.emptyInstance());
return new CompoundWrite(newWriteTree);
}
}
/**
* Returns whether this CompoundWrite will fully overwrite a node at a given location and can
* therefore be considered "complete".
*
* @param path The path to check for
* @return Whether there is a complete write at that path
*/
public boolean hasCompleteWrite(Path path) {
return getCompleteNode(path) != null;
}
public Node rootWrite() {
return this.writeTree.getValue();
}
/**
* Returns a node for a path if and only if the node is a "complete" overwrite at that path. This
* will not aggregate writes from deeper paths, but will return child nodes from a more shallow
* path.
*
* @param path The path to get a complete write
* @return The node if complete at that path, or null otherwise.
*/
public Node getCompleteNode(Path path) {
Path rootMost = this.writeTree.findRootMostPathWithValue(path);
if (rootMost != null) {
return this.writeTree.get(rootMost).getChild(Path.getRelative(rootMost, path));
} else {
return null;
}
}
/**
* Returns all children that are guaranteed to be a complete overwrite.
*
* @return A list of all complete children.
*/
public List getCompleteChildren() {
List children = new ArrayList<>();
if (this.writeTree.getValue() != null) {
for (NamedNode entry : this.writeTree.getValue()) {
children.add(new NamedNode(entry.getName(), entry.getNode()));
}
} else {
for (Map.Entry> entry : this.writeTree.getChildren()) {
ImmutableTree childTree = entry.getValue();
if (childTree.getValue() != null) {
children.add(new NamedNode(entry.getKey(), childTree.getValue()));
}
}
}
return children;
}
public CompoundWrite childCompoundWrite(Path path) {
if (path.isEmpty()) {
return this;
} else {
Node shadowingNode = this.getCompleteNode(path);
if (shadowingNode != null) {
return new CompoundWrite(new ImmutableTree<>(shadowingNode));
} else {
// let the constructor extract the priority update
return new CompoundWrite(this.writeTree.subtree(path));
}
}
}
public Map childCompoundWrites() {
Map children = new HashMap<>();
for (Map.Entry> entries : this.writeTree.getChildren()) {
children.put(entries.getKey(), new CompoundWrite(entries.getValue()));
}
return children;
}
/**
* Returns true if this CompoundWrite is empty and therefore does not modify any nodes.
*
* @return Whether this CompoundWrite is empty
*/
public boolean isEmpty() {
return this.writeTree.isEmpty();
}
private Node applySubtreeWrite(Path relativePath, ImmutableTree writeTree, Node node) {
if (writeTree.getValue() != null) {
// Since there a write is always a leaf, we're done here
return node.updateChild(relativePath, writeTree.getValue());
} else {
Node priorityWrite = null;
for (Map.Entry> childTreeEntry : writeTree.getChildren()) {
ImmutableTree childTree = childTreeEntry.getValue();
ChildKey childKey = childTreeEntry.getKey();
if (childKey.isPriorityChildName()) {
// Apply priorities at the end so we don't update priorities for either empty
// nodes or
// forget to apply priorities to empty nodes that are later filled
assert childTree.getValue() != null : "Priority writes must always be leaf nodes";
priorityWrite = childTree.getValue();
} else {
node = applySubtreeWrite(relativePath.child(childKey), childTree, node);
}
}
// If there was a priority write, we only apply it if the node is not empty
if (!node.getChild(relativePath).isEmpty() && priorityWrite != null) {
node = node.updateChild(relativePath.child(ChildKey.getPriorityKey()), priorityWrite);
}
return node;
}
}
/**
* Applies this CompoundWrite to a node. The node is returned with all writes from this
* CompoundWrite applied to the node.
*
* @param node The node to apply this CompoundWrite to
* @return The node with all writes applied
*/
public Node apply(Node node) {
return applySubtreeWrite(Path.getEmptyPath(), this.writeTree, node);
}
/**
* Returns a serializable version of this CompoundWrite.
*
* @param exportFormat Nodes to write are saved in their export format
* @return The map representing this CompoundWrite
*/
public Map getValue(final boolean exportFormat) {
final Map writes = new HashMap<>();
this.writeTree.foreach(
new ImmutableTree.TreeVisitor() {
@Override
public Void onNodeValue(Path relativePath, Node value, Void accum) {
writes.put(relativePath.wireFormat(), value.getValue(exportFormat));
return null;
}
});
return writes;
}
@Override
public Iterator> iterator() {
return this.writeTree.iterator();
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o == null || o.getClass() != this.getClass()) {
return false;
}
return ((CompoundWrite) o).getValue(true).equals(this.getValue(true));
}
@Override
public int hashCode() {
return this.getValue(true).hashCode();
}
@Override
public String toString() {
return "CompoundWrite{" + this.getValue(true).toString() + "}";
}
}