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

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

The newest version!

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

package io.permazen.util;

import com.google.common.base.Preconditions;

import java.util.Comparator;
import java.util.Iterator;
import java.util.NavigableSet;
import java.util.NoSuchElementException;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;

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

* For a read-only implementation, subclasses should implement {@link #comparator comparator()}, {@link #contains contains()}, * {@link #iterator iterator()}, and {@link #createSubSet createSubSet()} to handle reversed and restricted range sub-sets. * *

* For a mutable implementation, subclasses should also implement {@link #add add()}, {@link #remove remove()}, * {@link #clear clear()}, and make the {@link #iterator iterator()} mutable. * *

* All overridden methods must be aware of the {@linkplain #bounds range restriction bounds}, if any. * * @param element type */ public abstract class AbstractNavigableSet extends AbstractIterationSet implements NavigableSet { /** * Element range bounds associated with this instance. */ protected final Bounds bounds; /** * Convenience constructor for the case where there are no lower or upper bounds. */ protected AbstractNavigableSet() { this(new Bounds<>()); } /** * Primary constructor. * * @param bounds range restriction * @throws IllegalArgumentException if {@code bounds} is null */ protected AbstractNavigableSet(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 given element from this set if it is present. * *

* The implementation in {@link AbstractNavigableSet} always throws {@link UnsupportedOperationException}. */ @Override public boolean remove(Object elem) { throw new UnsupportedOperationException("read-only set"); } @Override public E first() { try (CloseableIterator i = this.iterator()) { return i.next(); } } @Override public E last() { try (CloseableIterator i = this.descendingIterator()) { return i.next(); } } @Override public E pollFirst() { try (CloseableIterator i = this.iterator()) { if (!i.hasNext()) return null; final E value = i.next(); i.remove(); return value; } } @Override public E pollLast() { return this.descendingSet().pollFirst(); } @Override public CloseableIterator descendingIterator() { return CloseableIterator.wrap(this.descendingSet().iterator()); } @Override public E lower(E elem) { return this.searchBelow(elem, false); } @Override public E floor(E elem) { return this.searchBelow(elem, true); } @Override public E ceiling(E elem) { return this.searchAbove(elem, true); } @Override public E higher(E elem) { return this.searchAbove(elem, false); } @Override public NavigableSet headSet(E newMaxElement) { return this.headSet(newMaxElement, false); } @Override public NavigableSet tailSet(E newMinElement) { return this.tailSet(newMinElement, true); } @Override public NavigableSet subSet(E newMinElement, E newMaxElement) { return this.subSet(newMinElement, true, newMaxElement, false); } @Override public NavigableSet descendingSet() { return this.createSubSet(true, this.bounds.reverse()); } @Override public NavigableSet headSet(E newMaxElement, boolean inclusive) { final Bounds newBounds = this.bounds.withUpperBound(newMaxElement, BoundType.of(inclusive)); if (newBounds.isInverted(this.comparator())) { throw new IllegalArgumentException(String.format( "upper bound %s < lower bound %s", newMaxElement, this.bounds.getLowerBound())); } if (!this.bounds.isWithinBounds(this.comparator(), newBounds)) { throw new IllegalArgumentException(String.format( "upper bound %s is out of bounds: %s", newMaxElement, this.bounds)); } return this.createSubSet(false, newBounds); } @Override public NavigableSet tailSet(E newMinElement, boolean inclusive) { final Bounds newBounds = this.bounds.withLowerBound(newMinElement, BoundType.of(inclusive)); if (newBounds.isInverted(this.comparator())) { throw new IllegalArgumentException(String.format( "lower bound %s > upper bound %s", newMinElement, this.bounds.getUpperBound())); } if (!this.bounds.isWithinBounds(this.comparator(), newBounds)) { throw new IllegalArgumentException(String.format( "lower bound %s is out of bounds: %s", newMinElement, this.bounds)); } return this.createSubSet(false, newBounds); } @Override public NavigableSet subSet(E newMinElement, boolean minInclusive, E newMaxElement, boolean maxInclusive) { final Bounds newBounds = new Bounds<>(newMinElement, BoundType.of(minInclusive), newMaxElement, BoundType.of(maxInclusive)); if (newBounds.isInverted(this.comparator())) throw new IllegalArgumentException(String.format("new bound(s) %s are backwards", newBounds)); if (!this.bounds.isWithinBounds(this.comparator(), newBounds)) throw new IllegalArgumentException(String.format("new bound(s) %s are out of bounds: %s", newBounds, this.bounds)); return this.createSubSet(false, newBounds); } @Override protected Spliterator buildSpliterator(final Iterator iterator) { return new Spliterators.AbstractSpliterator(Long.MAX_VALUE, Spliterator.ORDERED | Spliterator.SORTED | Spliterator.DISTINCT) { @Override public boolean tryAdvance(Consumer action) { if (!iterator.hasNext()) return false; action.accept(iterator.next()); return true; } @Override public Comparator getComparator() { return AbstractNavigableSet.this.comparator(); } }; } /** * Search for a lower element. Used to implement {@link #floor floor()} and {@link #lower lower()}. * *

* The implementation in {@link AbstractNavigableSet} checks the bounds then returns the first element from a head set. * * @param elem upper limit for search * @param inclusive true if {@code elem} itself is a candidate * @return highest element below {@code elem}, or null if not found */ protected E searchBelow(E elem, boolean inclusive) { if (!this.isWithinLowerBound(elem)) return null; final NavigableSet subSet = this.isWithinUpperBound(elem) ? this.headSet(elem, inclusive) : this; try { return subSet.last(); } catch (NoSuchElementException e) { return null; } } /** * Search for a higher element. Used to implement {@link #ceiling ceiling()} and {@link #higher higher()}. * *

* The implementation in {@link AbstractNavigableSet} checks the bounds then returns the first element from a tail set. * * @param elem lower limit for search * @param inclusive true if {@code elem} itself is a candidate * @return lowest element above {@code elem}, or null if not found */ protected E searchAbove(E elem, boolean inclusive) { if (!this.isWithinUpperBound(elem)) return null; final NavigableSet subSet = this.isWithinLowerBound(elem) ? this.tailSet(elem, inclusive) : this; try { return subSet.first(); } 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 set'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 NavigableSet createSubSet(boolean reverse, Bounds newBounds); /** * Determine if the given element is within this instance's lower bound (if any). * *

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

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





© 2015 - 2024 Weber Informatics LLC | Privacy Policy