![JAR search and dependency download from the Maven repository](/logo.png)
inet.ipaddr.format.util.AddressTrieSet Maven / Gradle / Ivy
Show all versions of ipaddress Show documentation
/*
* Copyright 2020 Sean C Foley
*
* 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
* or at
* https://github.com/seancfoley/IPAddress/blob/master/LICENSE
*
* 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 inet.ipaddr.format.util;
import java.io.Serializable;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.NavigableSet;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.Spliterator;
import java.util.function.Function;
import inet.ipaddr.Address;
import inet.ipaddr.format.util.AddressTrie.AddressBounds;
import inet.ipaddr.format.util.BinaryTreeNode.KeyIterator;
/**
* Wraps a {@link inet.ipaddr.format.util.AddressTrie} to view it as a Java Collections Framework set,
* implementing the {@link java.util.Set}, {@link java.util.SortedSet} and {@link java.util.NavigableSet} interfaces.
*
* Like {@link java.util.TreeSet}, this set is backed by a binary tree and implements the same interfaces that {@link java.util.TreeSet} does.
* But there are some significant differences between the two binary tree implementations.
* See {@link inet.ipaddr.format.util.AddressTrieMap} for a description of some of the differences.
* {@link java.util.TreeMap} is backed by a {@link java.util.TreeSet} and
* {@link inet.ipaddr.format.util.AddressTrieMap} is backed by an {@link inet.ipaddr.format.util.AddressTrie} just like {@link inet.ipaddr.format.util.AddressTrie},
* so all of the same implementation comparisons apply equally between the map implementations and the set implementations.
*
* With the trie set, only addresses that are either individual address or prefix block subnets of the same type and version can be added to the trie,
* see {@link inet.ipaddr.format.util.AddressTrie.AddressComparator} for a comparator for the ordering.
*
* Should you wish to store, in a collection, address instances that are not individual address or prefix block subnets,
* you can use {@link java.util.TreeSet} or any other Java collections framework set to store addresses of any type,
* or addresses of different versions or types in the same set,
* since all address items in this library are comparable with a natural ordering.
* There are additional orderings provided by this library as well, see {@link inet.ipaddr.AddressComparator}.
*
* @author scfoley
*
* @param the address type
*/
public class AddressTrieSet extends AbstractSet implements NavigableSet, Cloneable, Serializable {
private static final long serialVersionUID = 1L;
private AddressTrie trie; // the backing trie
private final boolean isReverse;
private final Range bounds;
private AddressTrieSet descending; //cached
public AddressTrieSet(AddressTrie trie) {
this.trie = trie;
this.isReverse = false;
this.bounds = null;
if(trie.set == null) {
trie.set = this;
}
}
public AddressTrieSet(AddressTrie trie, Collection extends E> collection) {
this.trie = trie;
this.isReverse = false;
this.bounds = null;
if(trie.set == null) {
trie.set = this;
}
addAll(collection);
}
AddressTrieSet(AddressTrie trie, Range bounds, boolean isReverse) {
this.trie = trie;
this.bounds = bounds;
this.isReverse = isReverse;
if(trie.set == null && !isReverse && bounds == null) {
trie.set = this;
}
}
public static class Range implements Serializable {
private static final long serialVersionUID = 1L;
final AddressBounds wrapped;
Range reversed;
final boolean isReverse;
Range(AddressBounds wrapped) {
this(wrapped, false);
}
Range(AddressBounds wrapped, boolean isReverse) {
if(wrapped == null) {
throw new NullPointerException();
}
this.wrapped = wrapped;
this.isReverse = isReverse;
}
Range reverse() {
Range result = reversed;
if(result == null) {
result = new Range(wrapped, !isReverse);
reversed = result;
result.reversed = this;
}
return result;
}
public boolean isInBounds(E addr) {
return isWithinLowerBound(addr) && isWithinUpperBound(addr);
}
public E getLowerBound() {
return isReverse ? wrapped.getUpperBound() : wrapped.getLowerBound();
}
public E getUpperBound() {
return isReverse ? wrapped.getLowerBound() : wrapped.getUpperBound();
}
public boolean lowerIsInclusive() {
return isReverse ? wrapped.upperIsInclusive() : wrapped.lowerIsInclusive();
}
public boolean upperIsInclusive() {
return isReverse ? wrapped.lowerIsInclusive() : wrapped.upperIsInclusive();
}
public boolean isLowerBounded() {
return getLowerBound() != null;
}
public boolean isUpperBounded() {
return getUpperBound() != null;
}
public boolean isBelowLowerBound(E addr) {
return isReverse ? wrapped.isAboveUpperBound(addr) : wrapped.isBelowLowerBound(addr);
}
public boolean isAboveUpperBound(E addr) {
return isReverse ? wrapped.isBelowLowerBound(addr) : wrapped.isAboveUpperBound(addr);
}
public boolean isWithinLowerBound(E addr) {
return !isBelowLowerBound(addr);
}
public boolean isWithinUpperBound(E addr) {
return !isAboveUpperBound(addr);
}
@Override
public String toString() {
Function super E, String> stringer = Address::toCanonicalString;
return AddressBounds.toString(
getLowerBound(), lowerIsInclusive(),
getUpperBound(), upperIsInclusive(), stringer, " -> ", stringer);
}
}
private boolean isBounded() {
return bounds != null;
}
@Override
public AddressTrieSet descendingSet() {
AddressTrieSet desc = descending;
if(desc == null) {
Range reverseBounds = isBounded() ? bounds.reverse() : null;
desc = new AddressTrieSet(trie, reverseBounds, !isReverse);
descending = desc;
desc.descending = this;
}
return desc;
}
/**
* Returns a trie representing this set.
*
* If this set has a restricted range, {@link #hasRestrictedRange()}, this generates a new trie for the set with only the nodes pertaining to the subset.
* Otherwise this returns the backing trie for this set.
*
* When a new trie is generated, the original backing trie for this set remains the same, it is not changed to the new trie.
*
* The returned trie will always have the same natural trie ordering,
* even if this set has the reverse ordering.
*
*/
public AddressTrie asTrie() {
if(isBounded()) {
return trie.clone();
}
if(!isReverse) {
trie.set = this;// in case we constructed the set first, we put a reference back to us
}
return trie;
}
/**
* Returns whether this set is the result of a call to {@link #headSet(Address)}, {@link #tailSet(Address)},
* {@link #subSet(Address, Address)} or any of the other six methods with the same names.
*
* @return
*/
public boolean hasRestrictedRange() {
return isBounded();
}
/**
* Returns the range if this set has a restricted range, see {@link #hasRestrictedRange()}. Otherwise returns null.
*
* @return
*/
public Range getRange() {
return bounds;
}
/**
* Returns the number of elements in this set.
* This is a constant time operation, unless the set has a restricted range, see {@link #hasRestrictedRange()},
* in which case it is a linear time operation proportional to the number of elements.
*
* @return the number of elements in this set
*/
@Override
public int size() {
return trie.size();
}
@Override
public boolean isEmpty() {
return trie.isEmpty();
}
@SuppressWarnings("unchecked")
@Override
public boolean contains(Object o) {
return trie.contains((E) o);
}
/**
* Adds the given single address or prefix block subnet to this set.
*
* If the given address is not a single address nor prefix block, then this method throws IllegalArgumentException.
*
* See {@link AddressTrie}
*/
@Override
public boolean add(E e) {
return trie.add(e);
}
@SuppressWarnings("unchecked")
@Override
public boolean remove(Object o) {
return trie.remove((E) o);
}
@Override
public void clear() {
trie.clear();
}
@Override
public int hashCode() {
return trie.hashCode();
}
@Override
public boolean equals(Object o) {
if(o instanceof AddressTrieSet>) {
AddressTrieSet> other = (AddressTrieSet>) o;
// note that isReverse is ignored, intentionally
// two sets are equal if they have the same elements
return trie.equals(other.trie);
}
return super.equals(o);
}
/**
* Clones the set along with the backing trie. If the set had a restricted range, the clone does not.
*/
@SuppressWarnings("unchecked")
@Override
public AddressTrieSet clone() {
try {
AddressTrieSet clone = (AddressTrieSet) super.clone();
clone.trie = trie.clone();
// cloned tries have no bounds, so we need to put the bounds back,
// even though there are no longer trie elements outside the bounds,
// they still need to be part of this set
clone.trie.bounds = trie.bounds;
clone.descending = null;
return clone;
} catch (CloneNotSupportedException cannotHappen) {
return null;
}
}
@Override
public boolean removeAll(Collection> collection) {
if(collection instanceof List || collection instanceof Queue || collection.size() < size()) {
boolean result = false;
for (Object object : collection) {
if(remove(object)) {
result = true;
}
}
return result;
}
return removeIf(collection::contains);
}
@Override
public Iterator iterator() {
return isReverse ? trie.descendingIterator() : trie.iterator();
}
@Override
public Iterator descendingIterator() {
return isReverse ? trie.iterator() : trie.descendingIterator();
}
/**
* Returns an iterator that visits containing subnet blocks before their contained addresses and subnet blocks.
*/
public Iterator containingFirstIterator() {
return new KeyIterator(trie.containingFirstIterator(!isReverse));
}
/**
* Returns an iterator that visits contained addresses and subnet blocks before their containing subnet blocks.
*/
public Iterator containedFirstIterator() {
return new KeyIterator(trie.containedFirstIterator(!isReverse));
}
@Override
public Spliterator spliterator() {
return isReverse ? trie.descendingSpliterator() : trie.spliterator();
}
@Override
public Comparator comparator() {
return isReverse ? AddressTrie.reverseComparator() : AddressTrie.comparator();
}
/**
* Iterates from largest prefix blocks to smallest to individual addresses.
* Blocks of equal size are iterated in set order.
*
* @return
*/
public Iterator blockSizeIterator() {
return new KeyIterator(trie.blockSizeNodeIterator(!isReverse));
}
private AddressTrieSet toSubSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) {
if(isReverse) {
E tmp = fromElement;
boolean tmpInc = fromInclusive;
fromElement = toElement;
fromInclusive = toInclusive;
toElement = tmp;
toInclusive = tmpInc;
}
Range range = bounds;
AddressBounds bounds, newBounds;
if(range != null) {
bounds = range.wrapped;
} else {
bounds = null;
}
if(bounds == null) {
newBounds = AddressBounds.createNewBounds(fromElement, fromInclusive, toElement, toInclusive, trie.getComparator());
} else {
newBounds = bounds.restrict(fromElement, fromInclusive, toElement, toInclusive);
}
if(newBounds == null) {
return this;
}
Range newRange = new Range(newBounds, isReverse);
return new AddressTrieSet(trie.createSubTrie(newBounds), newRange, isReverse);
}
@Override
public AddressTrieSet subSet(E fromElement, E toElement) {
return subSet(fromElement, true, toElement, false);
}
@Override
public AddressTrieSet subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) {
if(fromElement == null || toElement == null) {
throw new NullPointerException();
}
return toSubSet(fromElement, fromInclusive, toElement, toInclusive);
}
@Override
public AddressTrieSet headSet(E toElement) {
return headSet(toElement, false);
}
@Override
public AddressTrieSet headSet(E toElement, boolean inclusive) {
if(toElement == null) {
throw new NullPointerException();
}
return toSubSet(null, true, toElement, inclusive);
}
@Override
public AddressTrieSet tailSet(E fromElement) {
return tailSet(fromElement, true);
}
@Override
public AddressTrieSet tailSet(E fromElement, boolean inclusive) {
if(fromElement == null) {
throw new NullPointerException();
}
return toSubSet(fromElement, inclusive, null, false);
}
@Override
public E first() {
BinaryTreeNode first = isReverse ? trie.lastAddedNode() : trie.firstAddedNode();
if(first == null) {
throw new NoSuchElementException();
}
return first.getKey();
}
@Override
public E last() {
BinaryTreeNode last = isReverse ? trie.firstAddedNode() : trie.lastAddedNode();
if(last == null) {
throw new NoSuchElementException();
}
return last.getKey();
}
@Override
public E lower(E e) {
return isReverse ? trie.higher(e) : trie.lower(e);
}
@Override
public E floor(E e) {
return isReverse ? trie.ceiling(e) : trie.floor(e);
}
@Override
public E ceiling(E e) {
return isReverse ? trie.floor(e) : trie.ceiling(e);
}
@Override
public E higher(E e) {
return isReverse ? trie.lower(e) : trie.higher(e);
}
@Override
public E pollFirst() {
BinaryTreeNode first = isReverse ? trie.lastAddedNode() : trie.firstAddedNode();
if(first == null) {
return null;
}
first.remove();
return first.getKey();
}
@Override
public E pollLast() {
BinaryTreeNode last = isReverse ? trie.firstAddedNode() : trie.lastAddedNode();
if(last == null) {
return null;
}
last.remove();
return last.getKey();
}
public String toTrieString() {
return trie.toString();
}
/**
* Returns a subset consisting of those addresses in the set contained by the given address.
* The subset will have a restricted range matching the range of the given subnet or address.
*
* If the subset would be the same size as this set, then this set is returned.
* The subset will the same backing trie as this set.
*
* @param addr
* @return
*/
public AddressTrieSet elementsContainedBy(E addr) {
AddressTrie newTrie = trie.elementsContainedByToSubTrie(addr);
if(trie == newTrie) {
return this;
}
if(newTrie.bounds == null) {
return new AddressTrieSet(newTrie, null, isReverse);
}
Range newRange = new Range(newTrie.bounds, isReverse);
return new AddressTrieSet(newTrie, newRange, isReverse);
}
/**
* Returns a subset consisting of those addresses in the set that contain the given address.
* The subset will have the same restricted range (if any) as this set.
*
* If the subset would be the same size as this set, then this set is returned.
* Otherwise, the subset is backed by a new trie.
*
* @param addr
* @return
*/
public AddressTrieSet elementsContaining(E addr) {
AddressTrie newTrie = trie.elementsContainingToTrie(addr);
if(trie == newTrie) {
return this;
}
if(newTrie.bounds == null) {
return new AddressTrieSet(newTrie, null, isReverse);
}
Range newRange = new Range(newTrie.bounds, isReverse);
return new AddressTrieSet(newTrie, newRange, isReverse);
}
/**
* Returns true if a subnet or address in the set contains the given subnet or address.
*
* @param addr
* @return
*/
public boolean elementContains(E addr) {
return trie.elementContainsBounds(addr);
}
/**
* Returns the element with the longest prefix match with the given address.
* @param addr
* @return
*/
public E longestPrefixMatch(E addr) {
return trie.longestPrefixMatchBounds(addr);
}
}