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

com.google.firebase.database.snapshot.IndexedNode 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.collection.ImmutableSortedSet;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

/**
 * Represents a node together with an index. The index and node are updated in unison. In the case
 * where the index does not affect the ordering (i.e. the ordering is identical to the key ordering)
 * this class uses a fallback index to save memory. Everything operating on the index must special
 * case the fallback index.
 */
public class IndexedNode implements Iterable {

  /**
   * This is a sentinal value, so it's fine to just use null for the comparator as it will never be
   * invoked.
   */
  private static final ImmutableSortedSet FALLBACK_INDEX =
      new ImmutableSortedSet<>(Collections.emptyList(), null);

  private final Node node;
  private final Index index;
  /**
   * The indexed set is initialized lazily for cases where we don't need to access any order
   * specific methods
   */
  private ImmutableSortedSet indexed;

  private IndexedNode(Node node, Index index) {
    this.index = index;
    this.node = node;
    // Index lazily
    this.indexed = null;
  }

  private IndexedNode(Node node, Index index, ImmutableSortedSet indexed) {
    this.index = index;
    this.node = node;
    this.indexed = indexed;
  }

  public static IndexedNode from(Node node) {
    return new IndexedNode(node, PriorityIndex.getInstance());
  }

  public static IndexedNode from(Node node, Index index) {
    return new IndexedNode(node, index);
  }

  private void ensureIndexed() {
    if (this.indexed == null) {
      // Not indexed yet, create now
      if (this.index.equals(KeyIndex.getInstance())) {
        this.indexed = FALLBACK_INDEX;
      } else {
        List children = new ArrayList<>();
        boolean sawIndexedValue = false;
        for (NamedNode entry : node) {
          sawIndexedValue = sawIndexedValue || index.isDefinedOn(entry.getNode());
          NamedNode namedNode = new NamedNode(entry.getName(), entry.getNode());
          children.add(namedNode);
        }
        if (sawIndexedValue) {
          this.indexed = new ImmutableSortedSet<>(children, index);
        } else {
          this.indexed = FALLBACK_INDEX;
        }
      }
    }
  }

  public boolean hasIndex(Index index) {
    return this.index.equals(index);
  }

  public Node getNode() {
    return this.node;
  }

  @Override
  public Iterator iterator() {
    ensureIndexed();
    if (this.indexed == FALLBACK_INDEX) {
      return this.node.iterator();
    } else {
      return this.indexed.iterator();
    }
  }

  public Iterator reverseIterator() {
    ensureIndexed();
    if (this.indexed == FALLBACK_INDEX) {
      return this.node.reverseIterator();
    } else {
      return this.indexed.reverseIterator();
    }
  }

  public IndexedNode updateChild(ChildKey key, Node child) {
    Node newNode = this.node.updateImmediateChild(key, child);
    if (this.indexed == FALLBACK_INDEX && !this.index.isDefinedOn(child)) {
      // doesn't affect the index, no need to create an index
      return new IndexedNode(newNode, this.index, FALLBACK_INDEX);
    } else if (this.indexed == null || this.indexed == FALLBACK_INDEX) {
      // No need to index yet, index lazily
      return new IndexedNode(newNode, this.index, null);
    } else {
      Node oldChild = this.node.getImmediateChild(key);
      ImmutableSortedSet newIndexed = this.indexed.remove(new NamedNode(key, oldChild));
      if (!child.isEmpty()) {
        newIndexed = newIndexed.insert(new NamedNode(key, child));
      }
      return new IndexedNode(newNode, this.index, newIndexed);
    }
  }

  public IndexedNode updatePriority(Node priority) {
    return new IndexedNode(node.updatePriority(priority), this.index, this.indexed);
  }

  public NamedNode getFirstChild() {
    if (!(this.node instanceof ChildrenNode)) {
      return null;
    } else {
      ensureIndexed();
      if (this.indexed == FALLBACK_INDEX) {
        ChildKey firstKey = ((ChildrenNode) this.node).getFirstChildKey();
        return new NamedNode(firstKey, this.node.getImmediateChild(firstKey));
      } else {
        return this.indexed.getMinEntry();
      }
    }
  }

  public NamedNode getLastChild() {
    if (!(this.node instanceof ChildrenNode)) {
      return null;
    } else {
      ensureIndexed();
      if (this.indexed == FALLBACK_INDEX) {
        ChildKey lastKey = ((ChildrenNode) this.node).getLastChildKey();
        return new NamedNode(lastKey, this.node.getImmediateChild(lastKey));
      } else {
        return this.indexed.getMaxEntry();
      }
    }
  }

  public ChildKey getPredecessorChildName(ChildKey childKey, Node childNode, Index index) {
    if (!this.index.equals(KeyIndex.getInstance()) && !this.index.equals(index)) {
      throw new IllegalArgumentException("Index not available in IndexedNode!");
    }
    ensureIndexed();
    if (this.indexed == FALLBACK_INDEX) {
      return this.node.getPredecessorChildKey(childKey);
    } else {
      NamedNode node = this.indexed.getPredecessorEntry(new NamedNode(childKey, childNode));
      return node != null ? node.getName() : null;
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy