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

java.util.TreeMap Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * 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 java.util;

import static javaemul.internal.InternalPreconditions.checkConcurrentModification;
import static javaemul.internal.InternalPreconditions.checkCriticalArgument;
import static javaemul.internal.InternalPreconditions.checkElement;
import static javaemul.internal.InternalPreconditions.checkState;
import static javaemul.internal.InternalPreconditions.isApiChecked;

import java.io.Serializable;
import jsinterop.annotations.JsEnum;
import jsinterop.annotations.JsNonNull;

/**
 * A map whose entries are sorted by their keys. All optional operations such as {@link #put} and
 * {@link #remove} are supported.
 */
public class TreeMap extends AbstractMap
    implements SortedMap, NavigableMap, Serializable {

  private Comparator comparator;
  private Node root;
  private int size;
  private int modCount;

  public TreeMap() {
    this((Comparator) null);
  }

  public TreeMap(Comparator comparator) {
    this.comparator = Comparators.nullToNaturalOrder(comparator);
  }

  public TreeMap(Map copyFrom) {
    this();
    putAll(copyFrom);
  }

  public TreeMap(SortedMap copyFrom) {
    this(copyFrom.comparator());
    putAll(copyFrom);
  }

  @Override
  public int size() {
    return size;
  }

  @Override
  public boolean isEmpty() {
    return size == 0;
  }

  @Override
  public V get(Object key) {
    Entry entry = findByObject(key);
    return entry != null ? entry.getValue() : null;
  }

  @Override
  public boolean containsKey(Object key) {
    return findByObject(key) != null;
  }

  @Override
  public V put(K key, V value) {
    return putInternal(key, value);
  }

  @Override
  public V putIfAbsent(K key, V value) {
    return putInternalIfAbsent(key, value);
  }

  @Override
  public void clear() {
    root = null;
    size = 0;
    structureChanged();
  }

  private void structureChanged() {
    if (!isApiChecked()) {
      // Shouldn't be necessary but JsCompiler chokes on removing modCount so make sure we don't pay
      // cost for updating the field.
      return;
    }
    this.modCount++;
  }

  @Override
  public V remove(Object key) {
    Node node = removeInternalByKey(key);
    return node != null ? node.getValue() : null;
  }

  /*
   * AVL methods
   */
  @JsEnum
  private enum Relation {
    LOWER,
    FLOOR,
    CEILING,
    HIGHER,
    CREATE;

    /**
     * Returns a possibly-flipped relation for use in descending views.
     *
     * @param ascending false to flip; true to return this.
     */
    Relation forOrder(boolean ascending) {
      if (ascending) {
        return this;
      }
      switch (this) {
        case LOWER:
          return HIGHER;
        case FLOOR:
          return CEILING;
        case CEILING:
          return FLOOR;
        case HIGHER:
          return LOWER;
        default:
          throw new IllegalStateException();
      }
    }
  }

  private V putInternal(K key, V value) {
    return find(key, Relation.CREATE).setValue(value);
  }

  private V putInternalIfAbsent(K key, V value) {
    Node node = find(key, Relation.CREATE);
    V oldValue = node.getValue();
    if (oldValue == null) {
      node.setValue(value);
    }
    return oldValue;
  }

  /** Returns the node at or adjacent to the given key, creating it if requested. */
  private Node find(K key, Relation relation) {
    if (root == null) {
      if (relation == Relation.CREATE) {
        root = new Node(null, key);
        size = 1;
        structureChanged();
        return root;
      } else {
        return null;
      }
    }
    Node nearest = root;
    while (true) {
      int comparison = comparator.compare(key, nearest.getKey());

      // Found the requested key.
      if (comparison == 0) {
        switch (relation) {
          case LOWER:
            return nearest.prev();
          case FLOOR:
          case CEILING:
            return nearest;
          case HIGHER:
            return nearest.next();
          case CREATE:
            return nearest;
        }
      }

      Node child = (comparison < 0) ? nearest.left : nearest.right;
      if (child == null) {
        // Found a nearest node.
        // Every key not in the tree has up to two nearest nodes, one lower and one higher.
        switch (relation) {
          case LOWER:
          case FLOOR:
            return comparison < 0 ? nearest.prev() : nearest;
          case CEILING:
          case HIGHER:
            return comparison < 0 ? nearest : nearest.next();
          case CREATE:
            Node created = new Node(nearest, key);
            if (comparison < 0) {
              nearest.left = created;
            } else {
              nearest.right = created;
            }
            size++;
            structureChanged();
            rebalance(nearest, true);
            return created;
        }
      }
      nearest = child;
    }
  }

  private K findKey(K key, Relation relation) {
    return getKeyOrNull(find(key, relation));
  }

  private Entry findEntry(K key, Relation relation) {
    return immutableCopy(find(key, relation));
  }

  @SuppressWarnings("unchecked")
  private Node findByObject(Object key) {
    Node node = root;
    while (node != null) {
      int c = comparator.compare((K) key, node.getKey());
      if (c == 0) {
        return node;
      }
      node = c < 0 ? node.left : node.right;
    }
    return null;
  }

  /**
   * Returns this map's entry that has the same key and value as {@code entry}, or null if this map
   * has no such entry.
   *
   * 

This method uses the comparator for key equality rather than {@code equals}. If this map's * comparator isn't consistent with equals (such as {@code String.CASE_INSENSITIVE_ORDER}), then * {@code remove()} and {@code contains()} will violate the collections API. */ private Node findByEntry(Entry entry) { Node mine = findByObject(entry.getKey()); boolean valuesEqual = mine != null && Objects.equals(mine.getValue(), entry.getValue()); return valuesEqual ? mine : null; } /** Removes {@code node} from this tree, rearranging the tree's structure as necessary. */ private void removeInternal(Node node) { Node left = node.left; Node right = node.right; Node originalParent = node.parent; if (left != null && right != null) { /* * To remove a node with both left and right subtrees, move an * adjacent node from one of those subtrees into this node's place. * * Removing the adjacent node may change this node's subtrees. This * node may no longer have two subtrees once the adjacent node is * gone! */ Node adjacent = (left.height > right.height) ? left.last() : right.first(); removeInternal(adjacent); // takes care of rebalance and size-- int leftHeight = 0; left = node.left; if (left != null) { leftHeight = left.height; adjacent.left = left; left.parent = adjacent; node.left = null; } int rightHeight = 0; right = node.right; if (right != null) { rightHeight = right.height; adjacent.right = right; right.parent = adjacent; node.right = null; } adjacent.height = Math.max(leftHeight, rightHeight) + 1; replaceInParent(node, adjacent); return; } else if (left != null) { replaceInParent(node, left); node.left = null; } else if (right != null) { replaceInParent(node, right); node.right = null; } else { replaceInParent(node, null); } rebalance(originalParent, false); size--; structureChanged(); } private Node removeInternalByKey(Object key) { Node node = findByObject(key); if (node != null) { removeInternal(node); } return node; } private void replaceInParent(Node node, Node replacement) { Node parent = node.parent; node.parent = null; if (replacement != null) { replacement.parent = parent; } if (parent != null) { if (parent.left == node) { parent.left = replacement; } else { // assert (parent.right == node); parent.right = replacement; } } else { root = replacement; } } /** * Rebalances the tree by making any AVL rotations necessary between the newly-unbalanced node and * the tree's root. * * @param insert true if the node was unbalanced by an insert; false if it was by a removal. */ private void rebalance(Node unbalanced, boolean insert) { for (Node node = unbalanced; node != null; node = node.parent) { Node left = node.left; Node right = node.right; int leftHeight = left != null ? left.height : 0; int rightHeight = right != null ? right.height : 0; int delta = leftHeight - rightHeight; if (delta == -2) { Node rightLeft = right.left; Node rightRight = right.right; int rightRightHeight = rightRight != null ? rightRight.height : 0; int rightLeftHeight = rightLeft != null ? rightLeft.height : 0; int rightDelta = rightLeftHeight - rightRightHeight; if (rightDelta == -1 || (rightDelta == 0 && !insert)) { rotateLeft(node); // AVL right right } else { // assert (rightDelta == 1); rotateRight(right); // AVL right left rotateLeft(node); } if (insert) { break; // no further rotations will be necessary } } else if (delta == 2) { Node leftLeft = left.left; Node leftRight = left.right; int leftRightHeight = leftRight != null ? leftRight.height : 0; int leftLeftHeight = leftLeft != null ? leftLeft.height : 0; int leftDelta = leftLeftHeight - leftRightHeight; if (leftDelta == 1 || (leftDelta == 0 && !insert)) { rotateRight(node); // AVL left left } else { // assert (leftDelta == -1); rotateLeft(left); // AVL left right rotateRight(node); } if (insert) { break; // no further rotations will be necessary } } else if (delta == 0) { node.height = leftHeight + 1; // leftHeight == rightHeight if (insert) { break; // the insert caused balance, so rebalancing is done! } } else { // assert (delta == -1 || delta == 1); node.height = Math.max(leftHeight, rightHeight) + 1; if (!insert) { break; // the height hasn't changed, so rebalancing is done! } } } } /** Rotates the subtree so that its root's right child is the new root. */ private void rotateLeft(Node root) { Node left = root.left; Node pivot = root.right; Node pivotLeft = pivot.left; Node pivotRight = pivot.right; // move the pivot's left child to the root's right root.right = pivotLeft; if (pivotLeft != null) { pivotLeft.parent = root; } replaceInParent(root, pivot); // move the root to the pivot's left pivot.left = root; root.parent = pivot; // fix heights root.height = Math.max(left != null ? left.height : 0, pivotLeft != null ? pivotLeft.height : 0) + 1; pivot.height = Math.max(root.height, pivotRight != null ? pivotRight.height : 0) + 1; } /** Rotates the subtree so that its root's left child is the new root. */ private void rotateRight(Node root) { Node pivot = root.left; Node right = root.right; Node pivotLeft = pivot.left; Node pivotRight = pivot.right; // move the pivot's right child to the root's left root.left = pivotRight; if (pivotRight != null) { pivotRight.parent = root; } replaceInParent(root, pivot); // move the root to the pivot's right pivot.right = root; root.parent = pivot; // fixup heights root.height = Math.max(right != null ? right.height : 0, pivotRight != null ? pivotRight.height : 0) + 1; pivot.height = Math.max(root.height, pivotLeft != null ? pivotLeft.height : 0) + 1; } /* * Navigable methods. */ /** * Returns an immutable version of {@param entry}. Need this because we allow entry to be null, in * which case we return a null SimpleImmutableEntry. */ private SimpleImmutableEntry immutableCopy(Entry entry) { return entry == null ? null : new SimpleImmutableEntry(entry); } private Node getFirst() { return root == null ? null : root.first(); } private Node getLast() { return root == null ? null : root.last(); } @Override public Entry firstEntry() { return immutableCopy(getFirst()); } private Node internalPollFirstEntry() { if (root == null) { return null; } Node result = root.first(); removeInternal(result); return result; } @Override public Entry pollFirstEntry() { return immutableCopy(internalPollFirstEntry()); } @Override public K firstKey() { checkElement(root != null); return root.first().getKey(); } @Override public Entry lastEntry() { return immutableCopy(getLast()); } private Entry internalPollLastEntry() { if (root == null) { return null; } Node result = root.last(); removeInternal(result); return result; } @Override public Entry pollLastEntry() { return immutableCopy(internalPollLastEntry()); } @Override public K lastKey() { checkElement(root != null); return root.last().getKey(); } @Override public Entry lowerEntry(K key) { return findEntry(key, Relation.LOWER); } @Override public K lowerKey(K key) { return findKey(key, Relation.LOWER); } @Override public Entry floorEntry(K key) { return findEntry(key, Relation.FLOOR); } @Override public K floorKey(K key) { return findKey(key, Relation.FLOOR); } @Override public Entry ceilingEntry(K key) { return findEntry(key, Relation.CEILING); } @Override public K ceilingKey(K key) { return findKey(key, Relation.CEILING); } @Override public Entry higherEntry(K key) { return findEntry(key, Relation.HIGHER); } @Override public K higherKey(K key) { return findKey(key, Relation.HIGHER); } @Override public Comparator comparator() { return Comparators.naturalOrderToNull(comparator); } /* * View factory methods. */ private EntrySet entrySet; private KeySet keySet; @Override public Set> entrySet() { if (entrySet == null) { entrySet = new EntrySet(); } return entrySet; } @Override public @JsNonNull Set keySet() { return navigableKeySet(); } @Override public NavigableSet navigableKeySet() { if (keySet == null) { keySet = new KeySet(); } return keySet; } @Override public NavigableMap subMap(K from, boolean fromInclusive, K to, boolean toInclusive) { Bound fromBound = fromInclusive ? Bound.INCLUSIVE : Bound.EXCLUSIVE; Bound toBound = toInclusive ? Bound.INCLUSIVE : Bound.EXCLUSIVE; return new BoundedMap(true, from, fromBound, to, toBound); } @Override public SortedMap subMap(K fromInclusive, K toExclusive) { return new BoundedMap(true, fromInclusive, Bound.INCLUSIVE, toExclusive, Bound.EXCLUSIVE); } @Override public NavigableMap headMap(K to, boolean inclusive) { Bound toBound = inclusive ? Bound.INCLUSIVE : Bound.EXCLUSIVE; return new BoundedMap(true, null, Bound.NO_BOUND, to, toBound); } @Override public SortedMap headMap(K toExclusive) { return new BoundedMap(true, null, Bound.NO_BOUND, toExclusive, Bound.EXCLUSIVE); } @Override public NavigableMap tailMap(K from, boolean inclusive) { Bound fromBound = inclusive ? Bound.INCLUSIVE : Bound.EXCLUSIVE; return new BoundedMap(true, from, fromBound, null, Bound.NO_BOUND); } @Override public SortedMap tailMap(K fromInclusive) { return new BoundedMap(true, fromInclusive, Bound.INCLUSIVE, null, Bound.NO_BOUND); } @Override public NavigableMap descendingMap() { return new BoundedMap(false, null, Bound.NO_BOUND, null, Bound.NO_BOUND); } @Override public NavigableSet descendingKeySet() { return new BoundedMap(false, null, Bound.NO_BOUND, null, Bound.NO_BOUND).navigableKeySet(); } private static class Node extends SimpleEntry { Node parent; Node left; Node right; int height; Node(Node parent, K key) { super(key, null); this.parent = parent; this.height = 1; } /** * Returns the next node in an inorder traversal, or null if this is the last node in the tree. */ Node next() { if (right != null) { return right.first(); } Node node = this; Node parent = node.parent; while (parent != null) { if (parent.left == node) { return parent; } node = parent; parent = node.parent; } return null; } /** * Returns the previous node in an inorder traversal, or null if this is the first node in the * tree. */ Node prev() { if (left != null) { return left.last(); } Node node = this; Node parent = node.parent; while (parent != null) { if (parent.right == node) { return parent; } node = parent; parent = node.parent; } return null; } /** Returns the first node in this subtree. */ Node first() { Node node = this; Node child = node.left; while (child != null) { node = child; child = node.left; } return node; } /** Returns the last node in this subtree. */ Node last() { Node node = this; Node child = node.right; while (child != null) { node = child; child = node.right; } return node; } } /** * Walk the nodes of the tree left-to-right or right-to-left. Note that in descending iterations, * {@code next} will return the previous node. */ private abstract class MapIterator implements Iterator { protected Node next; protected Node last; protected int expectedModCount = modCount; MapIterator(Node next) { this.next = next; } @Override public boolean hasNext() { return next != null; } protected Node stepForward() { checkElement(next != null); checkConcurrentModification(modCount, expectedModCount); last = next; next = next.next(); return last; } protected Node stepBackward() { checkElement(next != null); checkConcurrentModification(modCount, expectedModCount); last = next; next = next.prev(); return last; } @Override public void remove() { checkState(last != null); removeInternal(last); expectedModCount = modCount; last = null; } } /* * View implementations. */ private class EntrySet extends AbstractSet> { @Override public int size() { return size; } @Override public Iterator> iterator() { return new MapIterator>(getFirst()) { @Override public Entry next() { return stepForward(); } }; } @Override public boolean contains(Object o) { return o instanceof Entry && findByEntry((Entry) o) != null; } @Override public boolean remove(Object o) { if (!(o instanceof Entry)) { return false; } Node node = findByEntry((Entry) o); if (node == null) { return false; } removeInternal(node); return true; } @Override public void clear() { TreeMap.this.clear(); } } private class KeySet extends AbstractSet implements NavigableSet { @Override public int size() { return size; } @Override public Iterator iterator() { return new MapIterator(getFirst()) { @Override public K next() { return stepForward().getKey(); } }; } @Override public Iterator descendingIterator() { return new MapIterator(getLast()) { @Override public K next() { return stepBackward().getKey(); } }; } @Override public boolean contains(Object o) { return containsKey(o); } @Override public boolean remove(Object key) { return removeInternalByKey(key) != null; } @Override public void clear() { TreeMap.this.clear(); } @Override public Comparator comparator() { return TreeMap.this.comparator(); } /* * Navigable methods. */ @Override public K first() { return firstKey(); } @Override public K last() { return lastKey(); } @Override public K lower(K key) { return lowerKey(key); } @Override public K floor(K key) { return floorKey(key); } @Override public K ceiling(K key) { return ceilingKey(key); } @Override public K higher(K key) { return higherKey(key); } @Override public K pollFirst() { return getKeyOrNull(internalPollFirstEntry()); } @Override public K pollLast() { return getKeyOrNull(internalPollLastEntry()); } /* * View factory methods. */ @Override public NavigableSet subSet(K from, boolean fromInclusive, K to, boolean toInclusive) { return TreeMap.this.subMap(from, fromInclusive, to, toInclusive).navigableKeySet(); } @Override public SortedSet subSet(K fromInclusive, K toExclusive) { return TreeMap.this.subMap(fromInclusive, true, toExclusive, false).navigableKeySet(); } @Override public NavigableSet headSet(K to, boolean inclusive) { return TreeMap.this.headMap(to, inclusive).navigableKeySet(); } @Override public SortedSet headSet(K toExclusive) { return TreeMap.this.headMap(toExclusive, false).navigableKeySet(); } @Override public NavigableSet tailSet(K from, boolean inclusive) { return TreeMap.this.tailMap(from, inclusive).navigableKeySet(); } @Override public SortedSet tailSet(K fromInclusive) { return TreeMap.this.tailMap(fromInclusive, true).navigableKeySet(); } @Override public NavigableSet descendingSet() { return new BoundedMap(false, null, Bound.NO_BOUND, null, Bound.NO_BOUND).navigableKeySet(); } } /* * Bounded views implementations. */ @JsEnum private enum Bound { INCLUSIVE, EXCLUSIVE, NO_BOUND } /** A map with optional limits on its range. */ private final class BoundedMap extends AbstractMap implements NavigableMap, Serializable { private final boolean ascending; private final K from; private final Bound fromBound; private final K to; private final Bound toBound; BoundedMap(boolean ascending, K from, Bound fromBound, K to, Bound toBound) { /* * Validate the bounds. In addition to checking that from <= to, we * verify that the comparator supports our bound objects. */ if (fromBound != Bound.NO_BOUND && toBound != Bound.NO_BOUND) { checkCriticalArgument(comparator.compare(from, to) <= 0); } else if (fromBound != Bound.NO_BOUND) { int unused = comparator.compare(from, from); } else if (toBound != Bound.NO_BOUND) { int unused = comparator.compare(to, to); } this.ascending = ascending; this.from = from; this.fromBound = fromBound; this.to = to; this.toBound = toBound; } @Override public boolean isEmpty() { return endpoint(true) == null; } @Override public V get(Object key) { return isInBounds(key) ? TreeMap.this.get(key) : null; } @Override public boolean containsKey(Object key) { return isInBounds(key) && TreeMap.this.containsKey(key); } @Override public V put(K key, V value) { checkInBounds(key, fromBound, toBound); return putInternal(key, value); } @Override public V putIfAbsent(K key, V value) { checkInBounds(key, fromBound, toBound); return putInternalIfAbsent(key, value); } @Override public V remove(Object key) { return isInBounds(key) ? TreeMap.this.remove(key) : null; } /** Returns true if the key is in bounds. */ @SuppressWarnings("unchecked") private boolean isInBounds(Object key) { return isInBounds((K) key, fromBound, toBound); } /** * Returns true if the key is in bounds. Use this overload with Bound.NO_BOUND to skip bounds * checking on either end. */ private boolean isInBounds(K key, Bound fromBound, Bound toBound) { if (fromBound == Bound.INCLUSIVE) { if (comparator.compare(key, from) < 0) { return false; // less than from } } else if (fromBound == Bound.EXCLUSIVE) { if (comparator.compare(key, from) <= 0) { return false; // less than or equal to from } } if (toBound == Bound.INCLUSIVE) { if (comparator.compare(key, to) > 0) { return false; // greater than 'to' } } else if (toBound == Bound.EXCLUSIVE) { if (comparator.compare(key, to) >= 0) { return false; // greater than or equal to 'to' } } return true; } private void checkInBounds(K key, Bound fromBound, Bound toBound) { if (!isInBounds(key, fromBound, toBound)) { checkCriticalArgument(false, key + " not in range " + from + ".." + to); } } /** Returns the entry if it is in bounds, or null if it is out of bounds. */ private Node bound(Node node, Bound fromBound, Bound toBound) { return node != null && isInBounds(node.getKey(), fromBound, toBound) ? node : null; } /* * Navigable methods. */ @Override public Entry firstEntry() { return immutableCopy(endpoint(true)); } @Override public Entry pollFirstEntry() { Node result = endpoint(true); if (result != null) { removeInternal(result); } return immutableCopy(result); } @Override public K firstKey() { Entry entry = endpoint(true); checkElement(entry != null); return entry.getKey(); } @Override public Entry lastEntry() { return immutableCopy(endpoint(false)); } @Override public Entry pollLastEntry() { Node result = endpoint(false); if (result != null) { removeInternal(result); } return immutableCopy(result); } @Override public K lastKey() { Entry entry = endpoint(false); checkElement(entry != null); return entry.getKey(); } /** * @param first true for the first element, false for the last. */ private Node endpoint(boolean first) { Node node = null; if (ascending == first) { switch (fromBound) { case NO_BOUND: node = getFirst(); break; case INCLUSIVE: node = find(from, Relation.CEILING); break; case EXCLUSIVE: node = find(from, Relation.HIGHER); break; } return bound(node, Bound.NO_BOUND, toBound); } else { switch (toBound) { case NO_BOUND: node = getLast(); break; case INCLUSIVE: node = find(to, Relation.FLOOR); break; case EXCLUSIVE: node = find(to, Relation.LOWER); break; } return bound(node, fromBound, Bound.NO_BOUND); } } /** * Performs a find on the underlying tree after constraining it to the bounds of this view. * Examples: * *

bound is (A..C) findBounded(B, FLOOR) stays source.find(B, FLOOR) * *

bound is (A..C) findBounded(C, FLOOR) becomes source.find(C, LOWER) * *

bound is (A..C) findBounded(D, LOWER) becomes source.find(C, LOWER) * *

bound is (A..C] findBounded(D, FLOOR) becomes source.find(C, FLOOR) * *

bound is (A..C] findBounded(D, LOWER) becomes source.find(C, FLOOR) */ private Node findBounded(K key, Relation relation) { relation = relation.forOrder(ascending); Bound fromBoundForCheck = fromBound; Bound toBoundForCheck = toBound; if (toBound != Bound.NO_BOUND && (relation == Relation.LOWER || relation == Relation.FLOOR)) { int comparison = comparator.compare(to, key); if (comparison <= 0) { key = to; if (toBound == Bound.EXCLUSIVE) { relation = Relation.LOWER; // 'to' is too high } else if (comparison < 0) { relation = Relation.FLOOR; // we already went lower } } toBoundForCheck = Bound.NO_BOUND; // we've already checked the upper bound } if (fromBound != Bound.NO_BOUND && (relation == Relation.CEILING || relation == Relation.HIGHER)) { int comparison = comparator.compare(from, key); if (comparison >= 0) { key = from; if (fromBound == Bound.EXCLUSIVE) { relation = Relation.HIGHER; // 'from' is too low } else if (comparison > 0) { relation = Relation.CEILING; // we already went higher } } fromBoundForCheck = Bound.NO_BOUND; // we've already checked the lower bound } return bound(find(key, relation), fromBoundForCheck, toBoundForCheck); } private K findBoundedKey(K key, Relation relation) { return getKeyOrNull(findBounded(key, relation)); } private Entry findBoundedEntry(K key, Relation relation) { return immutableCopy(findBounded(key, relation)); } @Override public Entry lowerEntry(K key) { return findBoundedEntry(key, Relation.LOWER); } @Override public K lowerKey(K key) { return findBoundedKey(key, Relation.LOWER); } @Override public Entry floorEntry(K key) { return findBoundedEntry(key, Relation.FLOOR); } @Override public K floorKey(K key) { return findBoundedKey(key, Relation.FLOOR); } @Override public Entry ceilingEntry(K key) { return findBoundedEntry(key, Relation.CEILING); } @Override public K ceilingKey(K key) { return findBoundedKey(key, Relation.CEILING); } @Override public Entry higherEntry(K key) { return findBoundedEntry(key, Relation.HIGHER); } @Override public K higherKey(K key) { return findBoundedKey(key, Relation.HIGHER); } @Override public Comparator comparator() { Comparator forward = TreeMap.this.comparator(); if (ascending) { return forward; } else { return Collections.reverseOrder(forward); } } /* * View factory methods. */ private BoundedEntrySet entrySet; private BoundedKeySet keySet; @Override public Set> entrySet() { if (entrySet == null) { entrySet = new BoundedEntrySet(); } return entrySet; } @Override public @JsNonNull Set keySet() { return navigableKeySet(); } @Override public NavigableSet navigableKeySet() { if (keySet == null) { keySet = new BoundedKeySet(); } return keySet; } @Override public NavigableMap descendingMap() { return new BoundedMap(!ascending, from, fromBound, to, toBound); } @Override public NavigableSet descendingKeySet() { return new BoundedMap(!ascending, from, fromBound, to, toBound).navigableKeySet(); } @Override public NavigableMap subMap(K from, boolean fromInclusive, K to, boolean toInclusive) { Bound fromBound = fromInclusive ? Bound.INCLUSIVE : Bound.EXCLUSIVE; Bound toBound = toInclusive ? Bound.INCLUSIVE : Bound.EXCLUSIVE; return subMap(from, fromBound, to, toBound); } @Override public NavigableMap subMap(K fromInclusive, K toExclusive) { return subMap(fromInclusive, Bound.INCLUSIVE, toExclusive, Bound.EXCLUSIVE); } @Override public NavigableMap headMap(K to, boolean inclusive) { Bound toBound = inclusive ? Bound.INCLUSIVE : Bound.EXCLUSIVE; return subMap(null, Bound.NO_BOUND, to, toBound); } @Override public NavigableMap headMap(K toExclusive) { return subMap(null, Bound.NO_BOUND, toExclusive, Bound.EXCLUSIVE); } @Override public NavigableMap tailMap(K from, boolean inclusive) { Bound fromBound = inclusive ? Bound.INCLUSIVE : Bound.EXCLUSIVE; return subMap(from, fromBound, null, Bound.NO_BOUND); } @Override public NavigableMap tailMap(K fromInclusive) { return subMap(fromInclusive, Bound.INCLUSIVE, null, Bound.NO_BOUND); } private NavigableMap subMap(K from, Bound fromBound, K to, Bound toBound) { if (!ascending) { K fromTmp = from; Bound fromBoundTmp = fromBound; from = to; fromBound = toBound; to = fromTmp; toBound = fromBoundTmp; } /* * If both the current and requested bounds are exclusive, the isInBounds check must be * inclusive. For example, to create (C..F) from (A..F), the bound 'F' is in bounds. */ if (fromBound == Bound.NO_BOUND) { from = this.from; fromBound = this.fromBound; } else { Bound fromBoundToCheck = fromBound == this.fromBound ? Bound.INCLUSIVE : this.fromBound; checkInBounds(from, fromBoundToCheck, this.toBound); } if (toBound == Bound.NO_BOUND) { to = this.to; toBound = this.toBound; } else { Bound toBoundToCheck = toBound == this.toBound ? Bound.INCLUSIVE : this.toBound; checkInBounds(to, this.fromBound, toBoundToCheck); } return new BoundedMap(ascending, from, fromBound, to, toBound); } /* * Bounded view implementations. */ private abstract class BoundedIterator extends MapIterator { protected BoundedIterator(Node next) { super(next); } @Override protected Node stepForward() { Node result = super.stepForward(); next = bound(next, Bound.NO_BOUND, toBound); return result; } @Override protected Node stepBackward() { Node result = super.stepBackward(); next = bound(next, fromBound, Bound.NO_BOUND); return result; } } private final class BoundedEntrySet extends AbstractSet> { @Override public int size() { int count = 0; for (Entry ignored : this) { count++; } return count; } @Override public boolean isEmpty() { return BoundedMap.this.isEmpty(); } @Override public Iterator> iterator() { return new BoundedIterator>(endpoint(true)) { @Override public Entry next() { return ascending ? stepForward() : stepBackward(); } }; } @Override public boolean contains(Object o) { if (!(o instanceof Entry)) { return false; } Entry entry = (Entry) o; return isInBounds(entry.getKey()) && findByEntry(entry) != null; } @Override public boolean remove(Object o) { if (!(o instanceof Entry)) { return false; } Entry entry = (Entry) o; return isInBounds(entry.getKey()) && TreeMap.this.entrySet().remove(entry); } } private final class BoundedKeySet extends AbstractSet implements NavigableSet { @Override public int size() { return BoundedMap.this.size(); } @Override public boolean isEmpty() { return BoundedMap.this.isEmpty(); } @Override public Iterator iterator() { return new BoundedIterator(endpoint(true)) { @Override public K next() { return (ascending ? stepForward() : stepBackward()).getKey(); } }; } @Override public Iterator descendingIterator() { return new BoundedIterator(endpoint(false)) { @Override public K next() { return (ascending ? stepBackward() : stepForward()).getKey(); } }; } @Override public boolean contains(Object key) { return isInBounds(key) && findByObject(key) != null; } @Override public boolean remove(Object key) { return isInBounds(key) && removeInternalByKey(key) != null; } /* * Navigable methods. */ @Override public K first() { return firstKey(); } @Override public K pollFirst() { return getKeyOrNull(internalPollFirstEntry()); } @Override public K last() { return lastKey(); } @Override public K pollLast() { return getKeyOrNull(internalPollLastEntry()); } @Override public K lower(K key) { return lowerKey(key); } @Override public K floor(K key) { return floorKey(key); } @Override public K ceiling(K key) { return ceilingKey(key); } @Override public K higher(K key) { return higherKey(key); } @Override public Comparator comparator() { return BoundedMap.this.comparator(); } /* * View factory methods. */ @Override public NavigableSet subSet(K from, boolean fromInclusive, K to, boolean toInclusive) { return subMap(from, fromInclusive, to, toInclusive).navigableKeySet(); } @Override public SortedSet subSet(K fromInclusive, K toExclusive) { return subMap(fromInclusive, toExclusive).navigableKeySet(); } @Override public NavigableSet headSet(K to, boolean inclusive) { return headMap(to, inclusive).navigableKeySet(); } @Override public SortedSet headSet(K toExclusive) { return headMap(toExclusive).navigableKeySet(); } @Override public NavigableSet tailSet(K from, boolean inclusive) { return tailMap(from, inclusive).navigableKeySet(); } @Override public SortedSet tailSet(K fromInclusive) { return tailMap(fromInclusive).navigableKeySet(); } @Override public NavigableSet descendingSet() { return new BoundedMap(!ascending, from, fromBound, to, toBound).navigableKeySet(); } } } private static K getKeyOrNull(Entry entry) { return entry == null ? null : entry.getKey(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy