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

com.google.firebase.database.core.utilities.ImmutableTree 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.core.utilities;

import com.google.firebase.database.collection.ImmutableSortedMap;
import com.google.firebase.database.collection.StandardComparator;
import com.google.firebase.database.core.Path;
import com.google.firebase.database.snapshot.ChildKey;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

@SuppressWarnings("rawtypes")
public class ImmutableTree implements Iterable> {

  private static final ImmutableSortedMap EMPTY_CHILDREN =
      ImmutableSortedMap.Builder.emptyMap(StandardComparator.getComparator(ChildKey.class));

  @SuppressWarnings("unchecked")
  private static final ImmutableTree EMPTY = new ImmutableTree<>(null, EMPTY_CHILDREN);

  private final T value;
  private final ImmutableSortedMap> children;

  public ImmutableTree(T value, ImmutableSortedMap> children) {
    this.value = value;
    this.children = children;
  }

  @SuppressWarnings("unchecked")
  public ImmutableTree(T value) {
    this(value, EMPTY_CHILDREN);
  }

  @SuppressWarnings("unchecked")
  public static  ImmutableTree emptyInstance() {
    return EMPTY;
  }

  public T getValue() {
    return this.value;
  }

  public ImmutableSortedMap> getChildren() {
    return this.children;
  }

  public boolean isEmpty() {
    return this.value == null && this.children.isEmpty();
  }

  public Path findRootMostMatchingPath(Path relativePath, Predicate predicate) {
    if (this.value != null && predicate.evaluate(this.value)) {
      return Path.getEmptyPath();
    } else {
      if (relativePath.isEmpty()) {
        return null;
      } else {
        ChildKey front = relativePath.getFront();
        ImmutableTree child = this.children.get(front);
        if (child != null) {
          Path path = child.findRootMostMatchingPath(relativePath.popFront(), predicate);
          if (path != null) {
            // TODO: this seems inefficient
            return new Path(front).child(path);
          } else {
            return null;
          }
        } else {
          return null;
        }
      }
    }
  }

  public Path findRootMostPathWithValue(Path relativePath) {
    return findRootMostMatchingPath(relativePath, Predicate.TRUE);
  }

  public T rootMostValue(Path relativePath) {
    return rootMostValueMatching(relativePath, Predicate.TRUE);
  }

  public T rootMostValueMatching(Path relativePath, Predicate predicate) {
    if (this.value != null && predicate.evaluate(this.value)) {
      return this.value;
    } else {
      ImmutableTree currentTree = this;
      for (ChildKey key : relativePath) {
        currentTree = currentTree.children.get(key);
        if (currentTree == null) {
          return null;
        } else if (currentTree.value != null && predicate.evaluate(currentTree.value)) {
          return currentTree.value;
        }
      }
      return null;
    }
  }

  public T leafMostValue(Path relativePath) {
    return leafMostValueMatching(relativePath, Predicate.TRUE);
  }

  /**
   * Returns the deepest value found between the root and the specified path that matches the
   * predicate.
   *
   * @param path Path along which to look for matching values.
   * @param predicate The predicate to evaluate values against.
   * @return The deepest matching value, or null if no value matches.
   */
  public T leafMostValueMatching(Path path, Predicate predicate) {
    T currentValue = (this.value != null && predicate.evaluate(this.value)) ? this.value : null;
    ImmutableTree currentTree = this;
    for (ChildKey key : path) {
      currentTree = currentTree.children.get(key);
      if (currentTree == null) {
        return currentValue;
      } else {
        if (currentTree.value != null && predicate.evaluate(currentTree.value)) {
          currentValue = currentTree.value;
        }
      }
    }
    return currentValue;
  }

  public boolean containsMatchingValue(Predicate predicate) {
    if (this.value != null && predicate.evaluate(this.value)) {
      return true;
    } else {
      for (Map.Entry> subtree : this.children) {
        if (subtree.getValue().containsMatchingValue(predicate)) {
          return true;
        }
      }
      return false;
    }
  }

  public ImmutableTree getChild(ChildKey child) {
    ImmutableTree childTree = this.children.get(child);
    if (childTree != null) {
      return childTree;
    } else {
      return emptyInstance();
    }
  }

  public ImmutableTree subtree(Path relativePath) {
    if (relativePath.isEmpty()) {
      return this;
    } else {
      ChildKey front = relativePath.getFront();
      ImmutableTree childTree = this.children.get(front);
      if (childTree != null) {
        return childTree.subtree(relativePath.popFront());
      } else {
        return emptyInstance();
      }
    }
  }

  public ImmutableTree set(Path relativePath, T value) {
    if (relativePath.isEmpty()) {
      return new ImmutableTree<>(value, this.children);
    } else {
      ChildKey front = relativePath.getFront();
      ImmutableTree child = this.children.get(front);
      if (child == null) {
        child = emptyInstance();
      }
      ImmutableTree newChild = child.set(relativePath.popFront(), value);
      ImmutableSortedMap> newChildren =
          this.children.insert(front, newChild);
      return new ImmutableTree<>(this.value, newChildren);
    }
  }

  public ImmutableTree remove(Path relativePath) {
    if (relativePath.isEmpty()) {
      if (this.children.isEmpty()) {
        return emptyInstance();
      } else {
        return new ImmutableTree<>(null, this.children);
      }
    } else {
      ChildKey front = relativePath.getFront();
      ImmutableTree child = this.children.get(front);
      if (child != null) {
        ImmutableTree newChild = child.remove(relativePath.popFront());
        ImmutableSortedMap> newChildren;
        if (newChild.isEmpty()) {
          newChildren = this.children.remove(front);
        } else {
          newChildren = this.children.insert(front, newChild);
        }
        if (this.value == null && newChildren.isEmpty()) {
          return emptyInstance();
        } else {
          return new ImmutableTree<>(this.value, newChildren);
        }
      } else {
        return this;
      }
    }
  }

  public T get(Path relativePath) {
    if (relativePath.isEmpty()) {
      return this.value;
    } else {
      ChildKey front = relativePath.getFront();
      ImmutableTree child = this.children.get(front);
      if (child != null) {
        return child.get(relativePath.popFront());
      } else {
        return null;
      }
    }
  }

  public ImmutableTree setTree(Path relativePath, ImmutableTree newTree) {
    if (relativePath.isEmpty()) {
      return newTree;
    } else {
      ChildKey front = relativePath.getFront();
      ImmutableTree child = this.children.get(front);
      if (child == null) {
        child = emptyInstance();
      }
      ImmutableTree newChild = child.setTree(relativePath.popFront(), newTree);
      ImmutableSortedMap> newChildren;
      if (newChild.isEmpty()) {
        newChildren = this.children.remove(front);
      } else {
        newChildren = this.children.insert(front, newChild);
      }
      return new ImmutableTree<>(this.value, newChildren);
    }
  }

  public void foreach(TreeVisitor visitor) {
    fold(Path.getEmptyPath(), visitor, null);
  }

  public  R fold(R accum, TreeVisitor visitor) {
    return fold(Path.getEmptyPath(), visitor, accum);
  }

  private  R fold(Path relativePath, TreeVisitor visitor, R accum) {
    for (Map.Entry> subtree : this.children) {
      accum = subtree.getValue().fold(relativePath.child(subtree.getKey()), visitor, accum);
    }
    if (this.value != null) {
      accum = visitor.onNodeValue(relativePath, this.value, accum);
    }
    return accum;
  }

  public Collection values() {
    final ArrayList list = new ArrayList<>();
    this.foreach(
        new TreeVisitor() {
          @Override
          public Void onNodeValue(Path relativePath, T value, Void accum) {
            list.add(value);
            return null;
          }
        });
    return list;
  }

  @Override
  public Iterator> iterator() {
    // This could probably be done more efficient than prefilling a list, however, it's also
    // a bit
    // tricky as we have to potentially scan all subtrees for a value that exists. Since
    // iterators
    // are consumed fully in most cases, this should give a fairly efficient implementation
    // in most
    // cases.
    final List> list = new ArrayList<>();
    this.foreach(
        new TreeVisitor() {
          @Override
          public Void onNodeValue(Path relativePath, T value, Void accum) {
            list.add(new AbstractMap.SimpleImmutableEntry<>(relativePath, value));
            return null;
          }
        });
    return list.iterator();
  }

  @Override
  public String toString() {
    StringBuilder builder = new StringBuilder();
    builder.append("ImmutableTree { value=");
    builder.append(getValue());
    builder.append(", children={");
    for (Map.Entry> child : children) {
      builder.append(child.getKey().asString());
      builder.append("=");
      builder.append(child.getValue());
    }
    builder.append("} }");
    return builder.toString();
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }

    ImmutableTree that = (ImmutableTree) o;

    if (children != null ? !children.equals(that.children) : that.children != null) {
      return false;
    }
    if (value != null ? !value.equals(that.value) : that.value != null) {
      return false;
    }

    return true;
  }

  @Override
  public int hashCode() {
    int result = value != null ? value.hashCode() : 0;
    result = 31 * result + (children != null ? children.hashCode() : 0);
    return result;
  }

  public interface TreeVisitor {

    R onNodeValue(Path relativePath, T value, R accum);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy