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

com.google.firebase.database.core.CompoundWrite Maven / Gradle / Ivy

Go to download

This is the official Firebase Admin Java SDK. Build extraordinary native JVM apps in minutes with Firebase. The Firebase platform can power your app’s backend, user authentication, static hosting, and more.

There is a newer version: 9.4.1
Show newest version
/*
 * 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() + "}";
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy