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

com.google.firebase.database.snapshot.ChildrenNode 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.

The 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.snapshot;

import com.google.firebase.database.collection.ImmutableSortedMap;
import com.google.firebase.database.collection.LLRBNode;
import com.google.firebase.database.core.Path;
import com.google.firebase.database.utilities.Utilities;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/** User: greg Date: 5/16/13 Time: 4:47 PM */
public class ChildrenNode implements Node {

  public static final Comparator NAME_ONLY_COMPARATOR =
      new Comparator() {
        @Override
        public int compare(ChildKey o1, ChildKey o2) {
          return o1.compareTo(o2);
        }
      };

  private final ImmutableSortedMap children;
  private final Node priority;

  private String lazyHash = null;

  protected ChildrenNode() {
    this.children = ImmutableSortedMap.Builder.emptyMap(NAME_ONLY_COMPARATOR);
    this.priority = PriorityUtilities.NullPriority();
  }

  protected ChildrenNode(ImmutableSortedMap children, Node priority) {
    if (children.isEmpty() && !priority.isEmpty()) {
      throw new IllegalArgumentException("Can't create empty ChildrenNode with priority!");
    }
    this.priority = priority;
    this.children = children;
  }

  private static void addIndentation(StringBuilder builder, int indentation) {
    for (int i = 0; i < indentation; i++) {
      builder.append(" ");
    }
  }

  @Override
  public boolean hasChild(ChildKey name) {
    return !this.getImmediateChild(name).isEmpty();
  }

  @Override
  public boolean isEmpty() {
    return children.isEmpty();
  }

  @Override
  public int getChildCount() {
    return children.size();
  }

  @Override
  public Object getValue() {
    return getValue(false);
  }

  @Override
  public Object getValue(boolean useExportFormat) {
    if (isEmpty()) {
      return null;
    }

    int numKeys = 0;
    int maxKey = 0;
    boolean allIntegerKeys = true;
    Map result = new HashMap<>();
    for (Map.Entry entry : children) {
      String key = entry.getKey().asString();
      result.put(key, entry.getValue().getValue(useExportFormat));
      numKeys++;
      // If we already found a string key, don't bother with any of this
      if (allIntegerKeys) {
        if (key.length() > 1 && key.charAt(0) == '0') {
          allIntegerKeys = false;
        } else {
          Integer keyAsInt = Utilities.tryParseInt(key);
          if (keyAsInt != null && keyAsInt >= 0) {
            if (keyAsInt > maxKey) {
              maxKey = keyAsInt;
            }
          } else {
            allIntegerKeys = false;
          }
        }
      }
    }

    if (!useExportFormat && allIntegerKeys && maxKey < 2 * numKeys) {
      // convert to an array
      List arrayResult = new ArrayList<>(maxKey + 1);
      for (int i = 0; i <= maxKey; ++i) {
        // Map.get will return null for non-existent values, so we don't have to worry about
        // filling them in manually
        arrayResult.add(result.get("" + i));
      }
      return arrayResult;
    } else {
      if (useExportFormat && !priority.isEmpty()) {
        result.put(".priority", priority.getValue());
      }
      return result;
    }
  }

  @Override
  public ChildKey getPredecessorChildKey(ChildKey childKey) {
    return this.children.getPredecessorKey(childKey);
  }

  @Override
  public ChildKey getSuccessorChildKey(ChildKey childKey) {
    return this.children.getSuccessorKey(childKey);
  }

  @Override
  public String getHashRepresentation(HashVersion version) {
    if (version != HashVersion.V1) {
      throw new IllegalArgumentException("Hashes on children nodes only supported for V1");
    }
    final StringBuilder toHash = new StringBuilder();
    if (!priority.isEmpty()) {
      toHash.append("priority:");
      toHash.append(priority.getHashRepresentation(HashVersion.V1));
      toHash.append(":");
    }
    List nodes = new ArrayList<>();
    boolean sawPriority = false;
    for (NamedNode node : this) {
      nodes.add(node);
      sawPriority = sawPriority || !node.getNode().getPriority().isEmpty();
    }
    if (sawPriority) {
      Collections.sort(nodes, PriorityIndex.getInstance());
    }
    for (NamedNode node : nodes) {
      String hashString = node.getNode().getHash();
      if (!hashString.equals("")) {
        toHash.append(":");
        toHash.append(node.getName().asString());
        toHash.append(":");
        toHash.append(hashString);
      }
    }
    return toHash.toString();
  }

  @Override
  public String getHash() {
    if (this.lazyHash == null) {
      String hashString = getHashRepresentation(HashVersion.V1);
      this.lazyHash = hashString.isEmpty() ? "" : Utilities.sha1HexDigest(hashString);
    }
    return this.lazyHash;
  }

  @Override
  public boolean isLeafNode() {
    return false;
  }

  @Override
  public Node getPriority() {
    return priority;
  }

  @Override
  public Node updatePriority(Node priority) {
    if (this.children.isEmpty()) {
      return EmptyNode.Empty();
    } else {
      return new ChildrenNode(this.children, priority);
    }
  }

  @Override
  public Node getImmediateChild(ChildKey name) {
    // Hack to treat priority as a regular child
    if (name.isPriorityChildName() && !this.priority.isEmpty()) {
      return this.priority;
    } else if (children.containsKey(name)) {
      return children.get(name);
    } else {
      return EmptyNode.Empty();
    }
  }

  @Override
  public Node getChild(Path path) {
    ChildKey front = path.getFront();
    if (front == null) {
      return this;
    } else {
      return getImmediateChild(front).getChild(path.popFront());
    }
  }

  public void forEachChild(final ChildVisitor visitor) {
    forEachChild(visitor, /*includePriority=*/ false);
  }

  public void forEachChild(final ChildVisitor visitor, boolean includePriority) {
    if (!includePriority || this.getPriority().isEmpty()) {
      children.inOrderTraversal(visitor);
    } else {
      children.inOrderTraversal(
          new LLRBNode.NodeVisitor() {
            boolean passedPriorityKey = false;

            @Override
            public void visitEntry(ChildKey key, Node value) {
              if (!passedPriorityKey && key.compareTo(ChildKey.getPriorityKey()) > 0) {
                passedPriorityKey = true;
                visitor.visitChild(ChildKey.getPriorityKey(), getPriority());
              }
              visitor.visitChild(key, value);
            }
          });
    }
  }

  public ChildKey getFirstChildKey() {
    return children.getMinKey();
  }

  public ChildKey getLastChildKey() {
    return children.getMaxKey();
  }

  @Override
  public Node updateChild(Path path, Node newChildNode) {
    ChildKey front = path.getFront();
    if (front == null) {
      return newChildNode;
    } else if (front.isPriorityChildName()) {
      assert PriorityUtilities.isValidPriority(newChildNode);
      return updatePriority(newChildNode);
    } else {
      Node newImmediateChild = getImmediateChild(front).updateChild(path.popFront(), newChildNode);
      return updateImmediateChild(front, newImmediateChild);
    }
  }

  @Override
  public Iterator iterator() {
    return new NamedNodeIterator(children.iterator());
  }

  @Override
  public Iterator reverseIterator() {
    return new NamedNodeIterator(children.reverseIterator());
  }

  @Override
  public Node updateImmediateChild(ChildKey key, Node newChildNode) {
    if (key.isPriorityChildName()) {
      return updatePriority(newChildNode);
    } else {
      ImmutableSortedMap newChildren = children;
      if (newChildren.containsKey(key)) {
        newChildren = newChildren.remove(key);
      }
      if (!newChildNode.isEmpty()) {
        newChildren = newChildren.insert(key, newChildNode);
      }
      if (newChildren.isEmpty()) {
        // Ignore priorities on empty nodes
        return EmptyNode.Empty();
      } else {
        return new ChildrenNode(newChildren, this.priority);
      }
    }
  }

  @Override
  public int compareTo(Node o) {
    if (this.isEmpty()) {
      if (o.isEmpty()) {
        return 0;
      } else {
        return -1;
      }
    } else if (o.isLeafNode()) {
      // Children nodes are greater than all leaf nodes
      return 1;
    } else if (o.isEmpty()) {
      return 1;
    } else if (o == Node.MAX_NODE) {
      return -1;
    } else {
      // Must be another Children node
      return 0;
    }
  }

  @Override
  public boolean equals(Object otherObj) {
    if (otherObj == null) {
      return false;
    }
    if (otherObj == this) {
      return true;
    }
    if (!(otherObj instanceof ChildrenNode)) {
      return false;
    }
    ChildrenNode other = (ChildrenNode) otherObj;
    if (!this.getPriority().equals(other.getPriority())) {
      return false;
    } else if (this.children.size() != other.children.size()) {
      return false;
    } else {
      Iterator> thisIterator = this.children.iterator();
      Iterator> otherIterator = other.children.iterator();
      while (thisIterator.hasNext() && otherIterator.hasNext()) {
        Map.Entry thisNameNode = thisIterator.next();
        Map.Entry otherNamedNode = otherIterator.next();
        if (!thisNameNode.getKey().equals(otherNamedNode.getKey())
            || !thisNameNode.getValue().equals(otherNamedNode.getValue())) {
          return false;
        }
      }
      if (thisIterator.hasNext() || otherIterator.hasNext()) {
        throw new IllegalStateException("Something went wrong internally.");
      }
      return true;
    }
  }

  @Override
  public int hashCode() {
    int hashCode = 0;
    for (NamedNode entry : this) {
      hashCode = 31 * hashCode + entry.getName().hashCode();
      hashCode = 17 * hashCode + entry.getNode().hashCode();
    }
    return hashCode;
  }

  @Override
  public String toString() {
    StringBuilder builder = new StringBuilder();
    toString(builder, 0);
    return builder.toString();
  }

  private void toString(StringBuilder builder, int indentation) {
    if (this.children.isEmpty() && this.priority.isEmpty()) {
      builder.append("{ }");
    } else {
      builder.append("{\n");
      for (Map.Entry childEntry : this.children) {
        addIndentation(builder, indentation + 2);
        builder.append(childEntry.getKey().asString());
        builder.append("=");
        if (childEntry.getValue() instanceof ChildrenNode) {
          ChildrenNode childrenNode = (ChildrenNode) childEntry.getValue();
          childrenNode.toString(builder, indentation + 2);
        } else {
          builder.append(childEntry.getValue().toString());
        }
        builder.append("\n");
      }
      if (!this.priority.isEmpty()) {
        addIndentation(builder, indentation + 2);
        builder.append(".priority=");
        builder.append(this.priority.toString());
        builder.append("\n");
      }
      addIndentation(builder, indentation);
      builder.append("}");
    }
  }

  private static class NamedNodeIterator implements Iterator {

    private final Iterator> iterator;

    public NamedNodeIterator(Iterator> iterator) {
      this.iterator = iterator;
    }

    @Override
    public boolean hasNext() {
      return iterator.hasNext();
    }

    @Override
    public NamedNode next() {
      Map.Entry entry = iterator.next();
      return new NamedNode(entry.getKey(), entry.getValue());
    }

    @Override
    public void remove() {
      iterator.remove();
    }
  }

  public abstract static class ChildVisitor extends LLRBNode.NodeVisitor {

    @Override
    public void visitEntry(ChildKey key, Node value) {
      visitChild(key, value);
    }

    public abstract void visitChild(ChildKey name, Node child);
  }
}