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

io.permazen.util.AbstractNavigableMap Maven / Gradle / Ivy

There is a newer version: 5.1.0
Show newest version

/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package io.permazen.util;

import com.google.common.base.Preconditions;

import java.util.AbstractMap;
import java.util.Comparator;
import java.util.Map;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.NoSuchElementException;

/**
 * Support superclass for {@link NavigableMap} implementations based on database entries.
 *
 * 

* For a read-only implementation, subclasses should implement {@link #comparator comparator()}, {@link #get get()}, * {@link #entrySet entrySet()}, {@link #navigableKeySet navigableKeySet()}, and {@link #createSubMap createSubMap()} * to handle reversed and restricted range sub-maps. * *

* For a mutable implementation, subclasses should also implement {@link #put put()}, {@link #remove remove()}, * {@link #clear clear()}, and make the {@link #navigableKeySet navigableKeySet()} and {@link #entrySet entrySet()} * iterators mutable. * *

* All overridden methods must be aware of the {@linkplain #bounds range restriction bounds}, if any. * * @param key type * @param value type */ public abstract class AbstractNavigableMap extends AbstractMap implements NavigableMap { /** * Key range bounds associated with this instance. */ protected final Bounds bounds; /** * Convenience constructor for the case where there are no lower or upper key bounds. */ protected AbstractNavigableMap() { this(new Bounds<>()); } /** * Primary constructor. * * @param bounds key range restriction * @throws IllegalArgumentException if {@code bounds} is null */ protected AbstractNavigableMap(Bounds bounds) { Preconditions.checkArgument(bounds != null, "null bounds"); this.bounds = bounds; } /** * Get the {@link Bounds} associated with this instance. * * @return range restriction */ public Bounds getBounds() { return this.bounds; } /** * Removes the mapping for a key from this map if it is present. * *

* The implementation in {@link AbstractNavigableMap} always throws {@link UnsupportedOperationException}. */ @Override public V remove(Object key) { throw new UnsupportedOperationException("read-only map"); } @Override public boolean isEmpty() { return this.navigableKeySet().isEmpty(); } @Override public boolean containsKey(Object obj) { return this.navigableKeySet().contains(obj); } @Override public K firstKey() { try (CloseableIterator i = CloseableIterator.wrap(this.navigableKeySet().iterator())) { return i.next(); } } @Override public K lastKey() { return this.descendingMap().firstKey(); } @Override public NavigableSet keySet() { return this.navigableKeySet(); } @Override public Map.Entry lowerEntry(K maxKey) { return this.searchBelow(maxKey, false); } @Override public Map.Entry floorEntry(K maxKey) { return this.searchBelow(maxKey, true); } @Override public Map.Entry ceilingEntry(K minKey) { return this.searchAbove(minKey, true); } @Override public Map.Entry higherEntry(K minKey) { return this.searchAbove(minKey, false); } @Override public K lowerKey(K maxKey) { final Map.Entry entry = this.lowerEntry(maxKey); return entry != null ? entry.getKey() : null; } @Override public K floorKey(K maxKey) { final Map.Entry entry = this.floorEntry(maxKey); return entry != null ? entry.getKey() : null; } @Override public K ceilingKey(K minKey) { final Map.Entry entry = this.ceilingEntry(minKey); return entry != null ? entry.getKey() : null; } @Override public K higherKey(K minKey) { final Map.Entry entry = this.higherEntry(minKey); return entry != null ? entry.getKey() : null; } @Override public Map.Entry firstEntry() { try (CloseableIterator> i = CloseableIterator.wrap(this.entrySet().iterator())) { return i.next(); } catch (NoSuchElementException e) { return null; } } @Override public Map.Entry lastEntry() { return this.descendingMap().firstEntry(); } @Override public Map.Entry pollFirstEntry() { try (CloseableIterator> i = CloseableIterator.wrap(this.entrySet().iterator())) { if (!i.hasNext()) return null; final Map.Entry entry = i.next(); i.remove(); return entry; } } @Override public Map.Entry pollLastEntry() { return this.descendingMap().pollFirstEntry(); } @Override public NavigableMap descendingMap() { return this.createSubMap(true, this.bounds.reverse()); } @Override public NavigableSet descendingKeySet() { return this.navigableKeySet().descendingSet(); } @Override public NavigableMap subMap(K minKey, K maxKey) { return this.subMap(minKey, true, maxKey, false); } @Override public NavigableMap headMap(K maxKey) { return this.headMap(maxKey, false); } @Override public NavigableMap tailMap(K minKey) { return this.tailMap(minKey, true); } @Override public NavigableMap headMap(K newMaxKey, boolean inclusive) { final Bounds newBounds = this.bounds.withUpperBound(newMaxKey, BoundType.of(inclusive)); if (newBounds.isInverted(this.comparator())) throw new IllegalArgumentException("upper bound " + newMaxKey + " < lower bound " + this.bounds.getLowerBound()); if (!this.bounds.isWithinBounds(this.comparator(), newBounds)) throw new IllegalArgumentException("upper bound " + newMaxKey + " is out of bounds: " + this.bounds); return this.createSubMap(false, newBounds); } @Override public NavigableMap tailMap(K newMinKey, boolean inclusive) { final Bounds newBounds = this.bounds.withLowerBound(newMinKey, BoundType.of(inclusive)); if (newBounds.isInverted(this.comparator())) throw new IllegalArgumentException("lower bound " + newMinKey + " > upper bound " + this.bounds.getUpperBound()); if (!this.bounds.isWithinBounds(this.comparator(), newBounds)) throw new IllegalArgumentException("lower bound " + newMinKey + " is out of bounds: " + this.bounds); return this.createSubMap(false, newBounds); } @Override public NavigableMap subMap(K newMinKey, boolean minInclusive, K newMaxKey, boolean maxInclusive) { final Bounds newBounds = new Bounds<>(newMinKey, BoundType.of(minInclusive), newMaxKey, BoundType.of(maxInclusive)); if (newBounds.isInverted(this.comparator())) throw new IllegalArgumentException("new bound(s) " + newBounds + " are backwards"); if (!this.bounds.isWithinBounds(this.comparator(), newBounds)) throw new IllegalArgumentException("new bound(s) " + newBounds + " are out of bounds: " + this.bounds); return this.createSubMap(false, newBounds); } /** * Search for a lower element. Used to implement {@link #floorEntry floorEntry()} and {@link #lowerEntry lowerEntry()}. * *

* The implementation in {@link AbstractNavigableMap} checks the bounds then returns the first entry from a head map. * * @param maxKey upper limit for search * @param inclusive true if {@code maxKey} itself is a candidate * @return highest element below {@code maxKey}, or null if not found */ protected Map.Entry searchBelow(K maxKey, boolean inclusive) { if (!this.isWithinLowerBound(maxKey)) return null; final NavigableMap subMap = this.isWithinUpperBound(maxKey) ? this.headMap(maxKey, inclusive) : this; try { return subMap.lastEntry(); } catch (NoSuchElementException e) { return null; } } /** * Search for a higher element. Used to implement {@link #ceilingEntry ceilingEntry()} and {@link #higherEntry higherEntry()}. * *

* The implementation in {@link AbstractNavigableMap} checks the bounds then returns the first entry from a tail map. * * @param minKey lower limit for search * @param inclusive true if {@code minKey} itself is a candidate * @return lowest element above {@code minKey}, or null if not found */ protected Map.Entry searchAbove(K minKey, boolean inclusive) { if (!this.isWithinUpperBound(minKey)) return null; final NavigableMap subMap = this.isWithinLowerBound(minKey) ? this.tailMap(minKey, inclusive) : this; try { return subMap.firstEntry(); } catch (NoSuchElementException e) { return null; } } /** * Get a non-null {@link Comparator} that sorts consistently with, and optionally reversed from, this instance. * * @param reversed whether to return a reversed {@link Comparator} * @return a non-null {@link Comparator} */ protected Comparator getComparator(boolean reversed) { return NavigableSets.getComparator(this.comparator(), reversed); } /** * Create a (possibly reversed) view of this instance with (possibly) tighter lower and/or upper bounds. * The {@code newBounds} are consistent with the new ordering (i.e., reversed relative to this instance's ordering if * {@code reverse} is true) and have already been range-checked against {@linkplain #bounds this instance's current bounds}. * * @param reverse whether the new map's ordering should be reversed relative to this instance's ordering * @param newBounds new bounds * @return restricted and/or reversed view of this instance * @throws IllegalArgumentException if {@code newBounds} is null * @throws IllegalArgumentException if a bound in {@code newBounds} is null and this set does not permit null elements */ protected abstract NavigableMap createSubMap(boolean reverse, Bounds newBounds); /** * Determine if the given element is within this instance's lower bound (if any). * *

* The implementation in {@link AbstractNavigableMap} returns {@code this.bounds.isWithinLowerBound(this.comparator(), elem)}. * * @param key map key * @return true if {@code elem} is within this instance's lower bound, or this instance has no lower bound */ protected boolean isWithinLowerBound(K key) { return this.bounds.isWithinLowerBound(this.comparator(), key); } /** * Determine if the given element is within this instance's upper bound (if any). * *

* The implementation in {@link AbstractNavigableMap} returns {@code this.bounds.isWithinUpperBound(this.comparator(), elem)}. * * @param key map key * @return true if {@code elem} is within this instance's upper bound, or this instance has no upper bound */ protected boolean isWithinUpperBound(K key) { return this.bounds.isWithinUpperBound(this.comparator(), key); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy