inet.ipaddr.format.util.AddressTrie 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.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Spliterator;
import java.util.function.Function;
import inet.ipaddr.Address;
import inet.ipaddr.AddressSegment;
import inet.ipaddr.AddressSegmentSeries;
import inet.ipaddr.IPAddress;
import inet.ipaddr.IPAddressSegment;
import inet.ipaddr.format.util.AssociativeAddressTrie.AssociativeTrieNode;
import inet.ipaddr.format.util.BinaryTreeNode.BlockSizeNodeIterator;
import inet.ipaddr.format.util.BinaryTreeNode.Bounds;
import inet.ipaddr.format.util.BinaryTreeNode.CachingIterator;
import inet.ipaddr.format.util.BinaryTreeNode.ChangeTracker;
import inet.ipaddr.format.util.BinaryTreeNode.ChangeTracker.Change;
import inet.ipaddr.format.util.BinaryTreeNode.Indents;
import inet.ipaddr.format.util.BinaryTreeNode.KeySpliterator;
import inet.ipaddr.format.util.BinaryTreeNode.NodeIterator;
import inet.ipaddr.format.util.BinaryTreeNode.NodeSpliterator;
import inet.ipaddr.format.util.BinaryTreeNode.PostOrderNodeIterator;
import inet.ipaddr.format.util.BinaryTreeNode.PreOrderNodeIterator;
import inet.ipaddr.ipv4.IPv4Address;
import inet.ipaddr.ipv6.IPv6Address;
/**
* A compact binary trie (aka compact binary prefix tree, or binary radix trie), for addresses and/or CIDR prefix block subnets.
* The prefixes in used by the prefix trie are the CIDR prefixes, or the full address in the case of individual addresses with no prefix length.
* The elements of the trie are CIDR prefix blocks or addresses.
*
* This trie data structure allows you to check an address for containment in many subnets at once, in constant time.
* The trie allows you to check a subnet for containment of many smaller subnets or addresses at once, in constant time.
* The trie allows you to check for equality of a subnet or address with a large number of subnets or addresses at once.
*
* The trie can also be used as the backing structure for a {@link AddressTrieSet} which is a {@link java.util.NavigableSet}.
* Unlike {@link java.util.TreeSet} this data structure provides access to the nodes and the associated subtrie with each node,
* which corresponds with their associated CIDR prefix block subnets.
*
* There is only a single possible trie for any given set of address and subnets. For one thing, this means they are automatically balanced.
* Also, this makes access to subtries and to the nodes themselves more useful, allowing for many of the same operations performed on the original trie.
*
* Each node has either a prefix block or a single address as its key.
* Each prefix block node can have two sub-nodes, each sub-node a prefix block or address contained by the node.
*
* There are more nodes in the trie than there are elements in the set.
* A node is considered "added" if it was explicitly added to the trie and is included as an element when viewed as a set.
* There are non-added prefix block nodes that are generated in the trie as well.
* When two or more added addresses share the same prefix up until they differ with the bit at index x,
* then a prefix block node is generated (if not already added to the trie) for the common prefix of length x,
* with the nodes for those addresses to be found following the lower
* or upper sub-nodes according to the bit at index x + 1 in each address.
* If that bit is 1, the node can be found by following the upper sub-node,
* and when it is 0, the lower sub-node.
*
* Nodes that were generated as part of the trie structure only
* because of other added elements are not elements of the represented set.
* The set elements are the elements that were explicitly added.
*
* You can work with parts of the trie, starting from any node in the trie,
* calling methods that start with any given node, such as iterating or spliterating the subtrie,
* finding the first or last in the subtrie, doing containment checks with the subtrie, and so on.
*
* The binary trie structure defines a natural ordering of the trie elements.
* Addresses of equal prefix length are sorted by prefix value. Addresses with no prefix length are sorted by address value.
* Addresses of differing prefix length are sorted according to the bit that follows the shorter prefix length in the address with the longer prefix length,
* whether that bit is 0 or 1 determines if that address is ordered before or after the address of shorter prefix length.
*
* The unique and pre-defined structure for a trie means that different means of traversing the trie can be more meaningful.
* This trie implementation provides 8 different ways of iterating through the trie:
*
- 1, 2: the natural sorted trie order, forward and reverse (spliterating is also an option for these two orders). Use {@link #nodeIterator(boolean)}, {@link #iterator()} or {@link #descendingIterator()}. A comparator is also provided for this order.
*
- 3, 4: pre-order tree traversal, in which parent node is visited before sub-nodes, with sub-nodes visited in forward or reverse order
*
- 5, 6: post-order tree traversal, in which sub-nodes are visited before parent nodes, with sub-nodes visited in forward or reverse order
*
- 7, 8: prefix-block order, in which larger prefix blocks are visited before smaller, and blocks of equal size are visited in forward or reverse sorted order
*
*
*
* All of these orderings are useful in specific contexts.
*
* You can do lookup and containment checks on all the subnets and addresses in the trie at once, in constant time.
* A generic trie data structure lookup is O(m) where m is the entry length.
* For this trie, which operates on address bits, entry length is capped at 128 bits for IPv6 and 32 bits for IPv4.
* That makes lookup a constant time operation.
* Subnet containment or equality checks are also constant time since they work the same way as lookup, by comparing prefix bits.
*
* For a generic trie data structure, construction is O(m * n) where m is entry length and n is the number of addresses,
* but for this trie, since entry length is capped at 128 bits for IPv6 and 32 bits for IPv4, construction is O(n),
* in linear proportion to the number of added elements.
*
* This trie also allows for constant time size queries (count of added elements, not node count), by storing sub-trie size in each node.
* It works by updating the size of every node in the path to any added or removed node.
* This does not change insertion or deletion operations from being constant time (because tree-depth is limited to address bit count).
* At the same this makes size queries constant time, rather than being O(n) time.
*
* This class is abstract and has a subclass for each address version or type.
* A single trie can use just a single address type or version, since it works with bits alone,
* and this cannot distinguish between different versions and types in the trie structure.
* More specifically, using different address bit lengths would:
*
* - break the concept of containment, for example IPv6 address 0::/8 would be considered to contain IPv4 address 0.2.3.4
*
- break the concept of equality, for example MAC 1:2:3:*:*:* and IPv4 1.2.3.0/24 would be considered the same since they have the same prefix bits and length
*
* Instead, you could aggregate multiple subtries to create a collection of multiple address types or versions.
* You can use the method {@link #toString(boolean, AddressTrie...)} for a String that represents multiple tries as a single tree.
*
* Tries are thread-safe when not being modified (elements added or removed), but are not thread-safe when one thread is modifying the trie.
* For thread safety when modifying, one option is to use {@link Collections#synchronizedNavigableSet(java.util.NavigableSet)} on {@link #asSet()}.
*
*
* @author scfoley
*
* @param the type of the address keys
*/
// Note: We do not allow direct access to tries that have non-null bounds.
// Such tries can only be accessed indirectly through the Set and Map classes.
// Methods like removeElementsContainedBy, elementsContainedBy, elementsContaining, and elementContains (and perhaps a couple others) would be inaccurate,
// as they do not account for the bounds.
// Those methods used by the Set and Map classes do account for the bounds.
// Also, many methods here give access to the nodes, and the nodes themselves do not account for the bounds.
// That in particular would make things quite confusing for users, in which the trie methods and the node methods produce different results.
//
// So overall, we do not allow direct access to AddressTrie objects that have bounds, mostly because of the potential confusion,
// and because it would force us to alter the API for methods like elementsContainedBy in a way that makes the API inferior.
//
// We do allow the Set and Map classes to produce an AddressTrie even when bounded,
// but that AddressTrie is a clone of the bounded trie that has only the bounded nodes.
// So, overall, we do provide the same functionality, you just have to generate a new trie from the bounded set or map.
//
// Also, by storing the bounds strictly inside the AddressTrie, we avoid the complications of making the Bounds part of the API,
// which would make all the operations quite tricky and in some cases expensive.
// For instance, here we can cache the bounded root and reuse it.
// Making the bounds part of the API would also double the API method count and make the API quite cumbersome,
// even if it is not public.
//
// So for all those reasons, the bounds are stored in the tries, but tries with bounds are not directly accessible.
// The API then remains quite full-fledged, with full access to the nodes, while at the same time the Set and Map API
// also remains full-fledged. Finally, Map and Set users can get a non-bounds trie for any bounded Set or Map,
// should they really want one.
//
public abstract class AddressTrie extends AbstractTree {
private static final long serialVersionUID = 1L;
protected static class AddressBounds extends Bounds {
private static final long serialVersionUID = 1L;
E oneAboveUpperBound, oneBelowUpperBound, oneAboveLowerBound, oneBelowLowerBound;
AddressBounds(E lowerBound, E upperBound, Comparator super E> comparator) {
this(lowerBound, true, upperBound, false, comparator);
}
AddressBounds(E lowerBound, boolean lowerInclusive, E upperBound, boolean upperInclusive, Comparator super E> comparator) {
super(lowerBound, lowerInclusive, upperBound, upperInclusive, comparator);
if(lowerBound != null) {
checkBlockOrAddress(lowerBound, true);
}
if(upperBound != null) {
checkBlockOrAddress(upperBound, true);
}
}
static AddressBounds createNewBounds(E lowerBound, boolean lowerInclusive, E upperBound, boolean upperInclusive, Comparator super E> comparator) {
if(lowerBound != null) {
if(lowerInclusive && lowerBound.isZero()) {
lowerBound = null;
}
}
if(upperBound != null) {
if(upperInclusive && upperBound.isMax()) {
upperBound = null;
}
}
if(lowerBound == null && upperBound == null) {
return null;
}
return new AddressBounds(lowerBound, lowerInclusive, upperBound, upperInclusive, comparator);
}
@Override
AddressBounds createBounds(E lowerBound, boolean lowerInclusive, E upperBound, boolean upperInclusive, Comparator super E> comparator) {
return new AddressBounds(lowerBound, lowerInclusive, upperBound, upperInclusive, comparator);
}
@Override
AddressBounds restrict(E lowerBound, boolean lowerInclusive, E upperBound, boolean upperInclusive) {
return (AddressBounds) super.restrict(lowerBound, lowerInclusive, upperBound, upperInclusive);
}
@Override
AddressBounds intersect(E lowerBound, boolean lowerInclusive, E upperBound, boolean upperInclusive) {
return (AddressBounds) super.intersect(lowerBound, lowerInclusive, upperBound, upperInclusive);
}
// matches the value just above the upper bound (only applies to discrete quantities)
@Override
boolean isAdjacentAboveUpperBound(E addr) {
E res = oneAboveUpperBound;
if(res == null) {
//res = (E) upperBound.increment(1);
res = increment(upperBound);
oneAboveUpperBound = res;
}
return res != null && res.equals(addr);
}
// matches the value just below the lower bound (only applies to discrete quantities)
@Override
boolean isAdjacentBelowLowerBound(E addr) {
E res = oneBelowLowerBound;
if(res == null) {
//res = (E) lowerBound.increment(-1);
res = decrement(lowerBound);
oneBelowLowerBound = res;
}
return res != null && res.equals(addr);
}
@Override
boolean isAdjacentBelowUpperBound(E addr) {
E res = oneBelowUpperBound;
if(res == null) {
res = decrement(upperBound);
//res = (E) upperBound.increment(-1);
oneBelowUpperBound = res;
}
return res != null && res.equals(addr);
}
// matches the value just below the lower bound (only applies to discrete quantities)
@Override
boolean isAdjacentAboveLowerBound(E addr) {
E res = oneAboveLowerBound;
if(res == null) {
res = increment(lowerBound);
//res = (E) lowerBound.increment(1);
oneAboveLowerBound = res;
}
return res != null && res.equals(addr);
}
@Override
boolean isMax(E addr) {
return addr.isMax();
}
@Override
boolean isMin(E addr) {
return addr.isZero();
}
@Override
public String toCanonicalString(String separator) {
Function super E, String> stringer = Address::toCanonicalString;
return toString(stringer, separator, stringer);
}
}
protected static enum Operation {
// Given an address/subnet key E
INSERT, // add node for E if not already there
REMAP, // alters nodes based on the existing nodes and their values
LOOKUP, // find node for E, traversing all containing elements along the way
NEAR, // closest match, going down trie to get element considered closest.
// Whether one thing is closer than another is determined by the sorted order.
// For example, for subnet 1.2.0.0/16, 1.2.128.0 is closest address on the high side, 1.2.127.255 is closest address on the low side
CONTAINING, // list the nodes whose keys contain E
INSERTED_DELETE, // remove node for E
SUBNET_DELETE // remove nodes whose keys are contained by E
}
// not optimized for size, since only temporary, to be used for a single operation
protected static class OpResult {
E addr;
// whether near is searching for a floor or ceiling
// a floor is greatest element below addr
// a ceiling is lowest element above addr
final boolean nearestFloor;
// whether near cannot be an exact match
final boolean nearExclusive;
final Operation op;
OpResult(E addr, Operation op) {
this(addr, op, false, false);
}
OpResult(E addr, boolean floor, boolean exclusive) {
this(addr, Operation.NEAR, floor, exclusive);
}
private OpResult(E addr, Operation op, boolean floor, boolean exclusive) {
this.addr = addr;
this.op = op;
this.nearestFloor = floor;
this.nearExclusive = exclusive;
}
// lookups:
// an inserted tree element matches the supplied argument
// exists is set to true only for "added" nodes
boolean exists;
// the matching tree element, when doing a lookup operation, or the pre-existing node for an insert operation
// existingNode is set for both added and not added nodes
TrieNode existingNode;
// the closest tree element, when doing a near operation
TrieNode nearestNode;
// if searching for a floor/lower, and the nearest node is above addr, then we must backtrack to get below
// if searching for a ceiling/higher, and the nearest node is below addr, then we must backtrack to get above
TrieNode backtrackNode;
//boolean backtrack;
// contains:
// A linked list of the tree elements, from largest to smallest,
// that contain the supplied argument, and the end of the list
TrieNode containing, containingEnd;
// The tree node with the smallest subnet or address containing the supplied argument
TrieNode smallestContaining;
// contained by:
// this tree is contained by the supplied argument
TrieNode containedBy;
// deletions:
// this tree was deleted
TrieNode deleted;
// adds and puts:
// new and existing values for add, put and remap operations
Object newValue, existingValue;
// this added tree node was newly created for an add
TrieNode inserted;
// this added tree node previously existed but had not been added yet
TrieNode added;
// this added tree node was already added to the trie
TrieNode addedAlready;
// remaps:
Function, ?> remapper;
static TrieNode getNextAdded(TrieNode node) {
while(node != null && !node.isAdded()) {
// Since only one of upper and lower can be populated, whether we start with upper or lower does not matter
TrieNode next = node.getUpperSubNode();
if(next == null) {
node = node.getLowerSubNode();
} else {
node = next;
}
}
return node;
}
TrieNode getContaining() {
TrieNode containing = getNextAdded(this.containing);
this.containing = containing;
if(containing != null) {
TrieNode current = containing;
do {
TrieNode next = current.getUpperSubNode();
TrieNode nextAdded;
if(next == null) {
next = current.getLowerSubNode();
nextAdded = getNextAdded(next);
if(next != nextAdded) {
current.setLower(nextAdded);
}
} else {
nextAdded = getNextAdded(next);
if(next != nextAdded) {
current.setUpper(nextAdded);
}
}
current = nextAdded;
} while(current != null);
}
return containing;
}
// add to the list of tree elements that contain the supplied argument
void addContaining(TrieNode containingSub) {
TrieNode cloned = containingSub.clone();
if(containing == null) {
containing = cloned;
} else {
Comparator> comp = nodeComparator();
if(comp.compare(containingEnd, cloned) > 0) {
containingEnd.setLower(cloned);
} else {
containingEnd.setUpper(cloned);
}
containingEnd.adjustCount(1);
}
containingEnd = cloned;
}
}
/**
* A comparator that provides the same ordering used by the trie,
* an ordering that works with prefix block subnets and individual addresses.
* The comparator is consistent with the equality and hashcode of address instances
* and can be used in other contexts. However, it only works with prefix blocks and individual addresses,
* not with addresses like 1-2.3.4.5-6 which cannot be differentiated using this comparator from 1.3.4.5
* and is thus not consistent with equals and hashcode for subnets that are not CIDR prefix blocks.
*
* The comparator first compares the prefix of addresses, with the full address value considered the prefix when
* there is no prefix length, ie when it is a single address. It takes the minimum m of the two prefix lengths and
* compares those m prefix bits in both addresses. The ordering is determined by which of those two values is smaller or larger.
*
* If those two values match, then it looks at the address with longer prefix.
* If both prefix lengths match then both addresses are equal.
* Otherwise it looks at bit m in the address with larger prefix. If 1 it is larger and if 0 it is smaller than the other.
*
* When comparing an address with a prefix p and an address without, the first p bits in both are compared, and if equal,
* the bit at index p in the non-prefixed address determines the ordering, if 1 it is larger and if 0 it is smaller than the other.
*
* When comparing an address with prefix length matching the bit count to an address with no prefix, they are considered equal if the bits match.
* For instance, 1.2.3.4/32 is equal to 1.2.3.4, and thus the trie does not allow 1.2.3.4/32 in the trie since it is indistinguishable from 1.2.3.4,
* instead 1.2.3.4/32 is converted to 1.2.3.4 when inserted into the trie.
*
* When comparing 0.0.0.0/0, which has no prefix, to other addresses, the first bit in the other address determines the ordering.
* If 1 it is larger and if 0 it is smaller than 0.0.0.0/0.
*
*
* @author scfoley
*
* @param
*/
public static class AddressComparator implements Comparator, Serializable {
private static final long serialVersionUID = 1L;
@Override
public int compare(E o1, E o2) {
if(o1 == o2) {
return 0;
}
int segmentCount = o1.getSegmentCount();
int bitsPerSegment = o1.getBitsPerSegment();
int bitsMatchedSoFar = 0;
int extraBits = Integer.SIZE - bitsPerSegment;
int i = 0;
while(true) {
AddressSegment segment1 = o1.getSegment(i);
AddressSegment segment2 = o2.getSegment(i);
Integer pref1 = getSegmentPrefLen(o1, bitsMatchedSoFar, segment1);
Integer pref2 = getSegmentPrefLen(o2, bitsMatchedSoFar, segment2);
int segmentPref2;
if(pref1 != null) {
int segmentPref1 = pref1;
if(pref2 != null && (segmentPref2 = pref2) <= segmentPref1) {
int matchingBits = getMatchingBits(segment1, segment2, segmentPref2, extraBits);
if(matchingBits >= segmentPref2) {
if(segmentPref2 == segmentPref1) {
// same prefix block
return 0;
} else {
// segmentPref2 is shorter prefix, prefix bits match, so depends on bit at index segmentPref2
return segment1.isOneBit(segmentPref2) ? 1 : -1;
}
} else {
return segment1.getSegmentValue() - segment2.getSegmentValue();
}
} else {
int matchingBits = getMatchingBits(segment1, segment2, segmentPref1, extraBits);
if(matchingBits >= segmentPref1) {
if(segmentPref1 < bitsPerSegment) {
return segment2.isOneBit(segmentPref1) ? -1 : 1;
} else if(++i == segmentCount) {
return 1; // o1 with prefix length matching bit count is the bigger
} // else must check the next segment
} else {
return segment1.getSegmentValue() - segment2.getSegmentValue();
}
}
} else if(pref2 != null) {
segmentPref2 = pref2;
int matchingBits = getMatchingBits(segment1, segment2, segmentPref2, extraBits);
if(matchingBits >= pref2) {
if(segmentPref2 < bitsPerSegment) {
return segment1.isOneBit(segmentPref2) ? 1 : -1;
} else if(++i == segmentCount) {
return -1; // o2 with prefix length matching bit count is the bigger
} // else must check the next segment
} else {
return segment1.getSegmentValue() - segment2.getSegmentValue();
}
} else {
int matchingBits = getMatchingBits(segment1, segment2, bitsPerSegment, extraBits);
if(matchingBits < bitsPerSegment) { // no match - the current subnet/address is not here
return segment1.getSegmentValue() - segment2.getSegmentValue();
} else if(++i == segmentCount) {
// same address
return 0;
} // else must check the next segment
}
bitsMatchedSoFar += bitsPerSegment;
}
}
}
/**
* Returns the next address according to the trie ordering
*
* @param
* @param addr
* @return
*/
@SuppressWarnings("unchecked")
public static E increment(E addr) {
if(addr.isMax()) {
return null;
}
if(addr instanceof IPAddress) {
IPAddress ipaddr = (IPAddress) addr;
if(addr.isPrefixed()) {
return (E) ipaddr.getUpper().setPrefixLength(ipaddr.getPrefixLength() + 1).toZeroHost();
}
return (E) ipaddr.toPrefixBlock(ipaddr.getBitCount() - (ipaddr.getTrailingBitCount(false) + 1));
}
if(addr.isPrefixed()) {
return (E) addr.getUpper().setPrefixLength(addr.getPrefixLength() + 1).toPrefixBlock().getLower();
}
int trailingBitCount = 0;
for(int i = addr.getSegmentCount() - 1; i >= 0; i--) {
AddressSegment seg = addr.getSegment(i);
if(!seg.isMax()) {
trailingBitCount += Integer.numberOfTrailingZeros(~seg.getSegmentValue());
break;
}
trailingBitCount += seg.getBitCount();
}
return (E) addr.setPrefixLength(addr.getBitCount() - (trailingBitCount + 1)).toPrefixBlock();
}
/**
* Returns the previous address according to the trie ordering
*
* @param
* @param addr
* @return
*/
@SuppressWarnings("unchecked")
public static E decrement(E addr) {
if(addr.isZero()) {
return null;
}
if(addr instanceof IPAddress) {
IPAddress ipaddr = (IPAddress) addr;
if(addr.isPrefixed()) {
return (E) ipaddr.getLower().setPrefixLength(ipaddr.getPrefixLength() + 1).toMaxHost();
}
return (E) ipaddr.toPrefixBlock(ipaddr.getBitCount() - (ipaddr.getTrailingBitCount(true) + 1));
}
if(addr.isPrefixed()) {
return (E) addr.getLower().setPrefixLength(addr.getPrefixLength() + 1).toPrefixBlock().getUpper();
}
int trailingBitCount = 0;
for(int i = addr.getSegmentCount() - 1; i >= 0; i--) {
AddressSegment seg = addr.getSegment(i);
if(!seg.isZero()) {
trailingBitCount += Integer.numberOfTrailingZeros(seg.getSegmentValue());
break;
}
trailingBitCount += seg.getBitCount();
}
return (E) addr.setPrefixLength(addr.getBitCount() - (trailingBitCount + 1)).toPrefixBlock();
}
public static class TrieComparator implements Comparator>, Serializable {
private static final long serialVersionUID = 1L;
Comparator comparator;
TrieComparator(Comparator comparator) {
this.comparator = comparator;
}
@Override
public int compare(BinaryTreeNode tree1, BinaryTreeNode tree2) {
E o1 = tree1.getKey();
E o2 = tree2.getKey();
return comparator.compare(o1, o2);
}
};
/**
* A node for a compact binary prefix trie whose elements are prefix block subnets or addresses,
*
* @author scfoley
*
* @param
*/
public static abstract class TrieNode extends BinaryTreeNode implements AddressTrieOps {
private static final long serialVersionUID = 1L;
protected TrieNode(E item) {
super(item);
}
/**
* Returns the node for the subnet block containing this node.
*
* @return
*/
@Override
public TrieNode getParent() {
return (TrieNode) super.getParent();
}
/**
* Returns the sub-node whose address is largest in value
*
* @return
*/
@Override
public TrieNode getUpperSubNode() {
return (TrieNode) super.getUpperSubNode();
}
/**
* Returns the sub node whose address is smallest in value
*
* @return
*/
@Override
public TrieNode getLowerSubNode() {
return (TrieNode) super.getLowerSubNode();
}
private TrieNode findNodeNear(E addr, boolean below, boolean exclusive) {
addr = checkBlockOrAddress(addr, true);
return findNodeNearNoCheck(addr, below, exclusive);
}
private TrieNode findNodeNearNoCheck(E addr, boolean below, boolean exclusive) {
OpResult result = new OpResult<>(addr, below, exclusive);
matchBits(result);
TrieNode backtrack = result.backtrackNode;
if(backtrack != null) {
TrieNode parent = backtrack.getParent();
while(parent != null &&
(backtrack == (below ? parent.getLowerSubNode() : parent.getUpperSubNode()))) {
backtrack = parent;
parent = backtrack.getParent();
}
if(parent != null) {
if(parent.isAdded()) {
result.nearestNode = parent;
} else {
result.nearestNode = (below ? parent.previousAddedNode() : parent.nextAddedNode());
}
}
}
return result.nearestNode;
}
@Override
public TrieNode previousAddedNode() {
return (TrieNode) super.previousAddedNode();
}
@Override
public TrieNode nextAddedNode() {
return (TrieNode) super.nextAddedNode();
}
@Override
public TrieNode nextNode() {
return (TrieNode) super.nextNode();
}
@Override
public TrieNode previousNode() {
return (TrieNode) super.previousNode();
}
@Override
public TrieNode firstNode() {
return (TrieNode) super.firstNode();
}
@Override
public TrieNode firstAddedNode() {
return (TrieNode) super.firstAddedNode();
}
@Override
public TrieNode lastNode() {
return (TrieNode) super.lastNode();
}
@Override
public TrieNode lastAddedNode() {
return (TrieNode) super.lastAddedNode();
}
@Override
public TrieNode lowerAddedNode(E addr) {
return findNodeNear(addr, true, true);
}
TrieNode lowerNodeNoCheck(E addr) {
return findNodeNearNoCheck(addr, true, true);
}
@Override
public TrieNode floorAddedNode(E addr) {
return findNodeNear(addr, true, false);
}
TrieNode floorNodeNoCheck(E addr) {
return findNodeNearNoCheck(addr, true, false);
}
@Override
public TrieNode higherAddedNode(E addr) {
return findNodeNear(addr, false, true);
}
TrieNode higherNodeNoCheck(E addr) {
return findNodeNearNoCheck(addr, false, true);
}
@Override
public TrieNode ceilingAddedNode(E addr) {
return findNodeNear(addr, false, false);
}
TrieNode ceilingNodeNoCheck(E addr) {
return findNodeNearNoCheck(addr, false, false);
}
@SuppressWarnings("unchecked")
@Override
public Iterator extends TrieNode> nodeIterator(boolean forward) {
return (Iterator extends TrieNode>) super.nodeIterator(forward);
}
@SuppressWarnings("unchecked")
@Override
public Iterator extends TrieNode> allNodeIterator(boolean forward) {
return (Iterator extends TrieNode>) super.allNodeIterator(forward);
}
/**
* Iterates the added nodes, ordered by keys from largest prefix blocks to smallest and then to individual addresses,
* in the sub-trie with this node as the root.
*
* This iterator supports the {@link java.util.Iterator#remove()} operation.
*
* @param lowerSubNodeFirst if true, for blocks of equal size the lower is first, otherwise the reverse order
* @return
*/
@SuppressWarnings("unchecked")
public Iterator extends TrieNode> blockSizeNodeIterator(boolean lowerSubNodeFirst) {
return (Iterator extends TrieNode>) super.blockSizeNodeIterator(lowerSubNodeFirst, true);
}
/**
* Iterates all the nodes, ordered by keys from largest prefix blocks to smallest and then to individual addresses,
* in the sub-trie with this node as the root.
*
* This iterator supports the {@link java.util.Iterator#remove()} operation.
*
* @param lowerSubNodeFirst if true, for blocks of equal size the lower is first, otherwise the reverse order
* @return
*/
@SuppressWarnings("unchecked")
public Iterator extends TrieNode> blockSizeAllNodeIterator(boolean lowerSubNodeFirst) {
return (Iterator extends TrieNode>) super.blockSizeNodeIterator(lowerSubNodeFirst, false);
}
/**
* Iterates all nodes, ordered by keys from largest prefix blocks to smallest and then to individual addresses,
* in the sub-trie with this node as the root.
*
* @return
*/
@SuppressWarnings("unchecked")
@Override
public CachingIterator extends TrieNode, E, C> blockSizeCachingAllNodeIterator() {
return (CachingIterator extends TrieNode, E, C>) super.blockSizeCachingAllNodeIterator();
}
@SuppressWarnings("unchecked")
@Override
public CachingIterator extends TrieNode, E, C> containingFirstIterator(boolean forwardSubNodeOrder) {
return (CachingIterator extends TrieNode, E, C>) super.containingFirstIterator(forwardSubNodeOrder);
}
@SuppressWarnings("unchecked")
@Override
public CachingIterator extends TrieNode, E, C> containingFirstAllNodeIterator(boolean forwardSubNodeOrder) {
return (CachingIterator extends TrieNode, E, C>) super.containingFirstAllNodeIterator(forwardSubNodeOrder);
}
@SuppressWarnings("unchecked")
@Override
public Iterator extends TrieNode> containedFirstIterator(boolean forwardSubNodeOrder) {
return (Iterator extends TrieNode>) super.containedFirstIterator(forwardSubNodeOrder);
}
@SuppressWarnings("unchecked")
@Override
public Iterator extends TrieNode> containedFirstAllNodeIterator(boolean forwardSubNodeOrder) {
return (Iterator extends TrieNode>) super.containedFirstAllNodeIterator(forwardSubNodeOrder);
}
@Override
public Spliterator extends TrieNode> nodeSpliterator(boolean forward) {
return nodeSpliterator(forward, true);
}
@Override
public Spliterator extends TrieNode> allNodeSpliterator(boolean forward) {
return nodeSpliterator(forward, false);
}
@SuppressWarnings("unchecked")
Spliterator extends TrieNode> nodeSpliterator(boolean forward, boolean addedNodesOnly) {
Comparator> comp = forward ? nodeComparator() : reverseNodeComparator();
Spliterator extends BinaryTreeNode> spliterator = new NodeSpliterator(
forward,
comp,
this,
forward ? firstNode() : lastNode(),
getParent(),
size(),
changeTracker,
addedNodesOnly /* added only */);
return (Spliterator extends TrieNode>) spliterator;
}
@Override
public Spliterator spliterator() {
return new KeySpliterator(nodeSpliterator(true, true), comparator());
}
@Override
public Spliterator descendingSpliterator() {
return new KeySpliterator(nodeSpliterator(false, true), reverseComparator());
}
@Override
public boolean contains(E addr) {
OpResult result = doLookup(addr);
return result.exists;
}
@Override
public boolean remove(E addr) {
addr = checkBlockOrAddress(addr, true);
OpResult result = new OpResult<>(addr, Operation.INSERTED_DELETE);
matchBits(result);
return result.exists;
}
@Override
public TrieNode getNode(E addr) {
OpResult result = doLookup(addr);
TrieNode ret = result.existingNode;
return ret;
}
@Override
public TrieNode removeElementsContainedBy(E addr) {
addr = checkBlockOrAddress(addr, true);
OpResult result = new OpResult<>(addr, Operation.SUBNET_DELETE);
matchBits(result);
return result.deleted;
}
@Override
public TrieNode elementsContainedBy(E addr) {
OpResult result = doLookup(addr);
return result.containedBy;
}
// only added nodes are added to the linked list
@Override
public TrieNode elementsContaining(E addr) {
addr = checkBlockOrAddress(addr, true);
OpResult result = new OpResult<>(addr, Operation.CONTAINING);
matchBits(result);
return result.getContaining();
}
@Override
public E longestPrefixMatch(E addr) {
TrieNode node = longestPrefixMatchNode(addr);
return node == null ? null : node.getKey();
}
// only added nodes are added to the linked list
@Override
public TrieNode longestPrefixMatchNode(E addr) {
return doLookup(addr).smallestContaining;
}
@Override
public boolean elementContains(E addr) {
return longestPrefixMatch(addr) != null;
}
private OpResult doLookup(E addr) {
addr = checkBlockOrAddress(addr, true);
OpResult result = new OpResult<>(addr, Operation.LOOKUP);
matchBits(result);
return result;
}
private void removeSubnet(OpResult result) {
result.deleted = this;
clear();
}
protected void remove(OpResult result) {
result.deleted = this;
remove();
}
void matchBits(OpResult result) {
matchBits(0, result);
}
void matchBits(int bitIndex, OpResult result) {
matchBits(this, bitIndex, result);
}
// traverses the tree, matching bits with prefix block nodes, until we can match no longer,
// at which point it completes the operation, whatever that operation is
static void matchBits(TrieNode node, int bitIndex, OpResult result) {
while(true) {
int bits = node.matchNodeBits(bitIndex, result);
if(bits >= 0) {
// matched all node bits up the given count, so move into sub-nodes
node = node.matchSubNode(bits, result);
if(node == null) {
// reached the end of the line
break;
}
// Matched a sub-node.
// The sub-node was chosen according to that next bit.
// That bit is therefore now a match,
// so increment the matched bits by 1, and keep going.
bitIndex = bits + 1;
} else {
// reached the end of the line
break;
}
}
}
int matchNodeBits(int bitIndex, OpResult result) {
E newAddr = result.addr;
Operation op = result.op;
AddressSegmentSeries existingAddr = getKey();
int bitsPerSegment = existingAddr.getBitsPerSegment();
int segmentIndex = bitIndex / bitsPerSegment;
int segmentCount = existingAddr.getSegmentCount();
// this block handles cases like handling 1.2.3.4 and 1.2.3.4/32
// but since those two return true for equals(), we do not allow both in our tries and we do not actually need this case,
// but we do keep it for alternative tries that do not need the collection to consistent with equals()
if(segmentIndex >= segmentCount) {
Integer existingPref = existingAddr.getPrefixLength();
Integer newPref = newAddr.getPrefixLength();
// note that "added" is already true here, we can only be here if explicitly inserted already
if(Objects.equals(existingPref, newPref)) {
result.containedBy = this;
handleMatch(result);
} else if(existingPref == null) {
result.containedBy = this;
handleContained(result, newPref);
} else { // newPref == null
handleContains(result);
return existingPref;
}
return -1;
}
if(newAddr.getSegmentCount() != segmentCount) {
// to handle this is tricky. For a:b:c:d:e:f I would need
// to convert to an address with prefix length 48, which I do not support.
// Not only that, the prefixed address would be equal with the original, which is an equality problem.
// So overall, it is not supported, it doesn't make sense.
//
// However, for MAC addresses, we do allow the first inserted address to determine the bit size of the trie.
throw new IllegalArgumentException(getMessage("ipaddress.error.mismatched.bit.size"));
}
int bitsMatchedSoFar = segmentIndex * bitsPerSegment;
int extraBits = Integer.SIZE - bitsPerSegment;
while(true) {
AddressSegment existingSegment = existingAddr.getSegment(segmentIndex);
AddressSegment newSegment = newAddr.getSegment(segmentIndex);
Integer segmentPref = getSegmentPrefLen(existingAddr, bitsMatchedSoFar, existingSegment);
Integer newPref = getSegmentPrefLen(newAddr, bitsMatchedSoFar, newSegment);
int newPrefixLen;
if(segmentPref != null) {
int segmentPrefLen = segmentPref;
if(newPref != null && (newPrefixLen = newPref) <= segmentPrefLen) {
int matchingBits = getMatchingBits(existingSegment, newSegment, newPrefixLen, extraBits);
if(matchingBits >= newPrefixLen) { // the bits of current prefix match
result.containedBy = this;
if(newPrefixLen == segmentPrefLen) {
if(isAdded()) {
handleMatch(result);
} else if(op == Operation.LOOKUP) {
result.existingNode = this;
} else if(op == Operation.INSERT) {
existingAdded(result);
} else if(op == Operation.SUBNET_DELETE) {
removeSubnet(result);
} else if(op == Operation.NEAR) {
findNearestFromMatch(result);
} else if(op == Operation.REMAP) {
remapNonAdded(result);
}
break;
} else { // newPrefixLen < segmentPrefLen, matchingBits >= newPrefixLen
handleContained(result, bitsMatchedSoFar + newPrefixLen);
}
} else {
// no match - the bits don't match
// matchingBits < newPrefLen < segmentPrefLen
handleSplitNode(result, bitsMatchedSoFar + matchingBits);
}
} else {
int matchingBits = getMatchingBits(existingSegment, newSegment, segmentPrefLen, extraBits);
if(matchingBits >= segmentPrefLen) { // match - the current subnet/address is a match so far, and we must go further to check smaller subnets
if(isAdded()) {
handleContains(result);
}
return segmentPrefLen + bitsMatchedSoFar;
} else {
// matchingBits < segmentPrefLen - no match - the bits in current prefix do not match the prefix of the existing address
handleSplitNode(result, bitsMatchedSoFar + matchingBits);
}
}
break;
} else if(newPref != null) {
newPrefixLen = newPref;
int matchingBits = getMatchingBits(existingSegment, newSegment, newPrefixLen, extraBits);
if(matchingBits >= newPrefixLen) { // the current bits match the current prefix, but the existing has no prefix
result.containedBy = this;
handleContained(result, bitsMatchedSoFar + newPrefixLen);
} else {
// no match - the current subnet does not match the existing address
handleSplitNode(result, bitsMatchedSoFar + matchingBits);
}
break;
} else {
int matchingBits = getMatchingBits(existingSegment, newSegment, bitsPerSegment, extraBits);
if(matchingBits < bitsPerSegment) { // no match - the current subnet/address is not here
handleSplitNode(result, bitsMatchedSoFar + matchingBits);
break;
} else if(++segmentIndex == segmentCount) { // match - the current subnet/address is a match
result.containedBy = this;
// note that "added" is already true here, we can only be here if explicitly inserted already since it is a non-prefixed full address
handleMatch(result);
break;
}
bitsMatchedSoFar += bitsPerSegment;
}
}
return -1;
}
private void handleContained(OpResult result, int newPref) {
Operation op = result.op;
if(op == Operation.INSERT) {
// if we have 1.2.3.4 and 1.2.3.4/32, and we are looking at the last segment,
// then there are no more bits to look at, and this makes the former a sub-node of the latter.
// In most cases, however, there are more bits in existingAddr, the latter, to look at.
replace(result, newPref);
} else if(op == Operation.SUBNET_DELETE) {
removeSubnet(result);
} else if(op == Operation.NEAR) {
findNearest(result, newPref);
} else if(op == Operation.REMAP) {
remapNonExistingReplace(result, newPref);
}
}
private boolean handleContains(OpResult result) {
result.smallestContaining = this;
if(result.op == Operation.CONTAINING) {
result.addContaining(this);
return true;
}
return false;
}
private void handleSplitNode(OpResult result, int totalMatchingBits) {
E newAddr = result.addr;
Operation op = result.op;
if(op == Operation.INSERT) {
split(result, totalMatchingBits, createNew(newAddr));
} else if(op == Operation.NEAR) {
findNearest(result, totalMatchingBits);
} else if(op == Operation.REMAP) {
remapNonExistingSplit(result, totalMatchingBits);
}
}
private void handleMatch(OpResult result) {
result.exists = true;
if(!handleContains(result)) {
Operation op = result.op;
if(op == Operation.LOOKUP) {
matched(result);
} else if(op == Operation.INSERT) {
matchedInserted(result);
} else if(op == Operation.INSERTED_DELETE) {
remove(result);
} else if(op == Operation.SUBNET_DELETE) {
removeSubnet(result);
} else if(op == Operation.NEAR) {
if(result.nearExclusive) {
findNearestFromMatch(result);
} else {
matched(result);
}
} else if(op == Operation.REMAP) {
remapMatch(result);
}
}
}
private void remapNonExistingReplace(OpResult result, int totalMatchingBits) {
if(remap(result, false)) {
replace(result, totalMatchingBits);
}
}
private void remapNonExistingSplit(OpResult result, int totalMatchingBits) {
if(remap(result, false)) {
split(result, totalMatchingBits, createNew(result.addr));
}
}
private TrieNode remapNonExisting(OpResult result) {
if(remap(result, false)) {
return createNew(result.addr);
}
return null;
}
private void remapNonAdded(OpResult result) {
if(remap(result, false)) {
existingAdded(result);
}
}
private void remapMatch(OpResult result) {
result.existingNode = this;
if(remap(result, true)) {
matchedInserted(result);
}
}
/**
* Remaps the value for a node to a new value.
* This operation, which works on mapped values, is for maps, so this base method here does nothing,
* but is overridden in map subclasses.
*
* @param result
* @param match
* @return true if a new node needs to be created (match is null) or added (match is non-null)
*/
boolean remap(OpResult result, boolean isMatch) {
return false;
}
// this node matched when doing a lookup
private void matched(OpResult result) {
result.existingNode = this;
result.nearestNode = this;
}
// ** overridden by map trie **
// similar to matched, but when inserting we see it already there.
// this added node had already been added before
void matchedInserted(OpResult result) {
result.existingNode = this;
result.addedAlready = this;
}
// this node previously existed but was not added til now
private void existingAdded(OpResult result) {
result.existingNode = this;
result.added = this;
added(result);
}
// this node is newly inserted and added
private void inserted(OpResult result) {
result.inserted = this;
added(result);
}
// ** overridden by map trie **
void added(OpResult result) {
setAdded(true);
adjustCount(1);
changeTracker.changed();
}
/**
* The current node and the new node both become sub-nodes of a new block node taking the position of the current node.
*
* @param totalMatchingBits
* @param newAddr
*/
@SuppressWarnings("unchecked")
private void split(OpResult result, int totalMatchingBits, TrieNode newSubNode) {
E newBlock = (E) getKey().setPrefixLength(totalMatchingBits).toPrefixBlock();
replace(newBlock, result, totalMatchingBits, newSubNode);
newSubNode.inserted(result);
}
/**
* The current node is replaced by the new node and becomes a sub-node of the new node.
*
* @param totalMatchingBits
* @param newAddr
*/
private void replace(OpResult result, int totalMatchingBits) {
result.containedBy = this;
TrieNode newNode = replace(result.addr, result, totalMatchingBits, null);
newNode.inserted(result);
}
/**
* The current node is replaced by a new block of the given address.
* The current node and given node become sub-nodes.
*
* @param newAssignedAddr
* @param result
* @param totalMatchingBits
* @param newSubNode
* @return
*/
private TrieNode replace(E newAssignedAddr, OpResult result, int totalMatchingBits, TrieNode newSubNode) {
TrieNode newNode = createNew(newAssignedAddr);
newNode.size = size;
TrieNode parent = getParent();
if(parent.getUpperSubNode() == this) {
parent.setUpper(newNode);
} else if(parent.getLowerSubNode() == this) {
parent.setLower(newNode);
}
E existingAddr = getKey();
if(totalMatchingBits < existingAddr.getBitCount() &&
existingAddr.isOneBit(totalMatchingBits)) {
if(newSubNode != null) {
newNode.setLower(newSubNode);
}
newNode.setUpper(this);
} else {
newNode.setLower(this);
if(newSubNode != null) {
newNode.setUpper(newSubNode);
}
}
return newNode;
}
// only called when lower/higher and not floor/ceiling since for a match ends things for the latter
private void findNearestFromMatch(OpResult result) {
if(result.nearestFloor) {
// looking for greatest element < queried address
// since we have matched the address, we must go lower again,
// and if we cannot, we must backtrack
TrieNode lower = getLowerSubNode();
if(lower == null) {
// no nearest node yet
result.backtrackNode = this;
} else {
TrieNode last;
do {
last = lower;
lower = lower.getUpperSubNode();
} while(lower != null);
result.nearestNode = last;
}
} else {
// looking for smallest element > queried address
TrieNode upper = getUpperSubNode();
if(upper == null) {
// no nearest node yet
result.backtrackNode = this;
} else {
TrieNode last;
do {
last = upper;
upper = upper.getLowerSubNode();
} while(upper != null);
result.nearestNode = last;
}
}
}
private void findNearest(OpResult result, int differingBitIndex) {
E thisAddr = getKey();
if(differingBitIndex < thisAddr.getBitCount() && thisAddr.isOneBit(differingBitIndex)) {
// this element and all below are > than the query address
if(result.nearestFloor) {
// looking for greatest element < or <= queried address, so no need to go further
// need to backtrack and find the last right turn to find node < than the query address again
result.backtrackNode = this;
} else {
// looking for smallest element > or >= queried address
TrieNode lower = this, last;
do {
last = lower;
lower = lower.getLowerSubNode();
} while(lower != null);
result.nearestNode = last;
}
} else {
// this element and all below are < than the query address
if(result.nearestFloor) {
// looking for greatest element < or <= queried address
TrieNode upper = this, last;
do {
last = upper;
upper = upper.getUpperSubNode();
} while(upper != null);
result.nearestNode = last;
} else {
// looking for smallest element > or >= queried address, so no need to go further
// need to backtrack and find the last left turn to find node > than the query address again
result.backtrackNode = this;
}
}
}
/**
* Initializes the tree with the given node
*
* @param node
*/
void init(TrieNode node) {
E newAddr = node.getKey();
if(newAddr.getBitCount() > 0 && newAddr.isOneBit(0)) {
setUpper(node);
} else {
setLower(node);
}
size = (isAdded() ? 1 : 0) + node.size;
}
private TrieNode matchSubNode(int bitIndex, OpResult result) {
E newAddr = result.addr;
if(!FREEZE_ROOT && isEmpty()) {
if(result.op == Operation.REMAP) {
remapNonAdded(result);
} else if(result.op == Operation.INSERT) {
setKey(newAddr);
existingAdded(result);
}
} else if(bitIndex < newAddr.getBitCount() && newAddr.isOneBit(bitIndex)) {
TrieNode upper = getUpperSubNode();
if(upper == null) {
// no match
Operation op = result.op;
if(op == Operation.INSERT) {
upper = createNew(newAddr);
setUpper(upper);
upper.inserted(result);
} else if(op == Operation.NEAR) {
if(result.nearestFloor) {
// With only one sub-node at most, normally that would mean this node must be added.
// But there is one exception, when we are the non-added root node.
// So must check for added here.
if(isAdded()) {
result.nearestNode = this;
} else {
// check if our lower sub-node is there and added. It is underneath addr too.
// find the highest node in that direction.
TrieNode lower = getLowerSubNode();
if(lower != null) {
TrieNode res = lower;
TrieNode next = res.getUpperSubNode();
while(next != null) {
res = next;
next = res.getUpperSubNode();
}
result.nearestNode = res;
}
}
} else {
result.backtrackNode = this;
}
} else if(op == Operation.REMAP) {
upper = remapNonExisting(result);
if(upper != null) {
setUpper(upper);
upper.inserted(result);
}
}
} else {
return upper;
}
} else {
// if we have 1.2.3.4 and 1.2.3.4/32, and we are looking at the last segment,
// then there are no more bits to look at, and this makes the former a sub-node of the latter.
// However, because 1.2.3.4 and 1.2.3.4/32 return true for equals(), we avoid putting both in the tree,
// and instead we always convert to 1.2.3.4 first.
// In most cases, however, there are more bits in newAddr, the former, to look at.
TrieNode lower = getLowerSubNode();
if(lower == null) {
// no match
Operation op = result.op;
if(op == Operation.INSERT) {
lower = createNew(newAddr);
setLower(lower);
lower.inserted(result);
} else if(op == Operation.NEAR) {
if(result.nearestFloor) {
result.backtrackNode = this;
} else {
// With only one sub-node at most, normally that would mean this node must be added.
// But there is one exception, when we are the non-added root node.
// So must check for added here.
if(isAdded()) {
result.nearestNode = this;
} else {
// check if our upper sub-node is there and added. It is above addr too.
// find the highest node in that direction.
TrieNode upper = getUpperSubNode();
if(upper != null) {
TrieNode res = upper;
TrieNode next = res.getLowerSubNode();
while(next != null) {
res = next;
next = res.getLowerSubNode();
}
result.nearestNode = res;
}
}
}
} else if(op == Operation.REMAP) {
lower = remapNonExisting(result);
if(lower != null) {
setLower(lower);
lower.inserted(result);
}
}
} else {
return lower;
}
}
return null;
}
private TrieNode createNew(E newAddr) {
TrieNode newNode = createNewImpl(newAddr);
newNode.changeTracker = changeTracker;
return newNode;
}
protected abstract TrieNode createNewImpl(E newAddr);
protected abstract AddressTrie createNewTree();
/**
* Creates a new sub-trie, copying the nodes starting with this node as root.
* The nodes are copies of the nodes in this sub-trie, but their keys and values are not copies.
*/
public AddressTrie asNewTrie() {
AddressTrie newTrie = createNewTree();
newTrie.addTrie(this);
return newTrie;
}
@Override
public TrieNode cloneTree() {
return (TrieNode) super.cloneTree();
}
@Override
public TrieNode clone() {
return (TrieNode) super.clone();
}
@Override
TrieNode cloneTree(Bounds bounds) {
return (TrieNode) super.cloneTree(bounds);
}
@Override
public boolean equals(Object o) {
return o instanceof TrieNode && super.equals(o);
}
}
static final TrieComparator> comparator = new TrieComparator<>(new AddressComparator<>());
static final TrieComparator> reverseComparator = new TrieComparator<>(Collections.reverseOrder(new AddressComparator<>()));
AddressTrieSet set;
AddressBounds bounds;
private TrieNode subRoot; // if bounded, the root of the subtrie, which can change
private Change subRootChange; // if trie was modified since last check for subroot, must check for new subroot
protected AddressTrie(TrieNode root) {
super(root);
root.changeTracker = new ChangeTracker();
}
protected AddressTrie(TrieNode root, AddressBounds bounds) {
super(root);
if(root.changeTracker == null) {
root.changeTracker = new ChangeTracker();
}
this.bounds = bounds;
}
private static Integer getSegmentPrefLen(
AddressSegmentSeries addr,
int bitsMatchedSoFar,
AddressSegment segment) {
if(segment instanceof IPAddressSegment) {
return ((IPAddressSegment) segment).getSegmentPrefixLength();
} else if(addr.isPrefixed()) {
int existingPrefLen = addr.getPrefixLength();
if(existingPrefLen <= bitsMatchedSoFar + addr.getBitsPerSegment()) {
Integer result = existingPrefLen - bitsMatchedSoFar;
if(result < 0) {
result = 0;
}
return result;
}
}
return null;
}
private static int getMatchingBits(AddressSegment segment1, AddressSegment segment2, int maxBits, int adjustment) {
if(maxBits == 0) {
return 0;
}
int val1 = segment1.getSegmentValue();
int val2 = segment2.getSegmentValue();
int xor = val1 ^ val2;
if(adjustment == IPv6Address.BITS_PER_SEGMENT) {
return numberOfLeadingZerosShort(xor);
} else if(adjustment == (32 - IPv4Address.BITS_PER_SEGMENT)) {
return numberOfLeadingZerosByte(xor);
}
return Integer.numberOfLeadingZeros(xor) - adjustment;
}
private static int numberOfLeadingZerosShort(int i) {
if (i == 0)
return 16;
int n = 1;
if (i >>> 8 == 0) { n += 8; i <<= 8; }
if (i >>> 12 == 0) { n += 4; i <<= 4; }
if (i >>> 14 == 0) { n += 2; i <<= 2; }
n -= i >>> 15;
return n;
}
private static int numberOfLeadingZerosByte(int i) {
if (i == 0)
return 8;
int n = 1;
if (i >>> 4 == 0) { n += 4; i <<= 4; }
if (i >>> 6 == 0) { n += 2; i <<= 2; }
n -= i >>> 7;
return n;
}
@Override
public boolean isEmpty() {
if(bounds == null) {
return super.isEmpty();
}
// we avoid calculating size for bounded tries
return firstAddedNode() == null;
}
/**
* Returns the number of nodes in the trie, which is more than the number of elements.
*
* @return
*/
@Override
public int nodeSize() {
if(bounds == null) {
return super.nodeSize();
}
int totalCount = 0;
Iterator extends TrieNode> iterator = allNodeIterator(true);
while(iterator.hasNext()) {
totalCount++;
iterator.next();
}
return totalCount;
}
@Override
public int size() {
if(bounds == null) {
return super.size();
}
int totalCount = 0;
Iterator extends TrieNode> iterator = nodeIterator(true);
while(iterator.hasNext()) {
TrieNode node = iterator.next();
if(node.isAdded() && bounds.isInBounds(node.getKey())) {
totalCount++;
}
}
return totalCount;
}
@Override
public boolean add(E addr) {
addr = checkBlockOrAddress(addr, true);
if(bounds != null) {
if(!bounds.isInBounds(addr)) {
throwOutOfBounds();
}
}
adjustRoot(addr);
TrieNode root = absoluteRoot();
OpResult result = new OpResult<>(addr, Operation.INSERT);
root.matchBits(result);
return !result.exists;
}
static void throwOutOfBounds() {
throw new IllegalArgumentException(getMessage("ipaddress.error.address.out.of.range"));
}
protected void adjustRoot(E addr) {}
@Override
public TrieNode addNode(E addr) {
addr = checkBlockOrAddress(addr, true);
if(bounds != null) {
if(!bounds.isInBounds(addr)) {
throwOutOfBounds();
}
}
adjustRoot(addr);
TrieNode root = absoluteRoot();
OpResult result = new OpResult<>(addr, Operation.INSERT);
root.matchBits(result);
TrieNode node = result.existingNode;
if(node == null) {
node = result.inserted;
}
return node;
}
/**
* Provides an associative trie in which the root and each added node are mapped to a list of their respective direct added nodes.
* This trie provides an alternative non-binary tree structure of the added nodes.
* It is used by {@link #toAddedNodesTreeString()} to produce a string showing the alternative structure.
* If there are no non-added nodes in this trie, then the alternative tree structure provided by this method is the same as the original trie.
*
* @return
*/
public abstract AssociativeAddressTrie>> constructAddedNodesTree();
/**
* Provides a flattened version of the trie showing only the contained added nodes and their containment structure, which is non-binary.
* The root node is included, which may or may not be added.
*
* See {@link #constructAddedNodesTree()}
*
* @return
*/
@SuppressWarnings("unchecked")
public String toAddedNodesTreeString() {
AssociativeAddressTrie>> addedTree = constructAddedNodesTree();
class IndentsNode {
Indents indents;
AssociativeTrieNode>> node;
IndentsNode(Indents indents, AssociativeTrieNode>> node) {
this.indents = indents;
this.node = node;
}
}
Deque stack = null;
AssociativeTrieNode>> root = addedTree.absoluteRoot();
StringBuilder builder = new StringBuilder();
builder.append('\n');
AssociativeTrieNode>> nextNode = (AssociativeTrieNode>>) root;
String nodeIndent = "", subNodeIndent = "";
IndentsNode nextItem;
while(true) {
builder.append(nodeIndent).
append(nextNode.isAdded() ? BinaryTreeNode.ADDED_NODE_CIRCLE : BinaryTreeNode.NON_ADDED_NODE_CIRCLE).
append(' ').append(nextNode.getKey()).append('\n');
List> nextNodes = nextNode.getValue();
if(nextNodes != null && nextNodes.size() > 0) {
AssociativeTrieNode nNode;
AssociativeTrieNode>> next;
int i = nextNodes.size() - 1;
Indents lastIndents = new Indents(
subNodeIndent + BinaryTreeNode.RIGHT_ELBOW,
subNodeIndent + BinaryTreeNode.BELOW_ELBOWS);
nNode = nextNodes.get(i);
next = (AssociativeTrieNode>>) nNode;
if(stack == null) {
stack = new ArrayDeque<>(addedTree.size());
}
stack.addFirst(new IndentsNode(lastIndents, next));
if(nextNodes.size() > 1) {
Indents firstIndents = new Indents(
subNodeIndent + BinaryTreeNode.LEFT_ELBOW,
subNodeIndent + BinaryTreeNode.IN_BETWEEN_ELBOWS);
for(--i; i >= 0; i--) {
nNode = nextNodes.get(i);
next = (AssociativeTrieNode>>) nNode;
stack.addFirst(new IndentsNode(firstIndents, next));
}
}
}
if(stack == null) {
break;
}
nextItem = stack.pollFirst();
if(nextItem == null) {
break;
}
nextNode = nextItem.node;
Indents nextIndents = nextItem.indents;
nodeIndent = nextIndents.nodeIndent;
subNodeIndent = nextIndents.subNodeInd;
}
return builder.toString();
}
/**
* Constructs a trie in which added nodes are mapped to their list of added sub-nodes.
*
* @return
*/
@SuppressWarnings("unchecked")
protected void contructAddedTree(AssociativeAddressTrie>> emptyTrie) {
emptyTrie.addTrie(absoluteRoot());
CachingIterator extends AssociativeTrieNode>>, E,
AssociativeTrieNode>>> iterator =
emptyTrie.containingFirstAllNodeIterator(true);
while(iterator.hasNext()) {
AssociativeTrieNode>> next = (AssociativeTrieNode>>) iterator.next(), parent;
// cache this node with its sub-nodes
iterator.cacheWithLowerSubNode(next);
iterator.cacheWithUpperSubNode(next);
// the cached object is our parent
if(next.isAdded()) {
parent = iterator.getCached();
if(parent != null) {
// find added parent, or the root if no added parent
// this part would be tricky if we accounted for the bounds,
// maybe we'd have to filter on the bounds, and also look for the sub-root
while(!parent.isAdded()) {
AssociativeTrieNode>> parentParent = parent.getParent();
if(parentParent == null) {
break;
}
parent = parentParent;
}
// store ourselves with that added parent or root
List> addedSubs = (List>) parent.getValue();
if(addedSubs == null) {
addedSubs = new ArrayList>(next.size() - 1);
parent.setValue(addedSubs);
}
addedSubs.add(next);
} // else root
}
}
Iterator extends AssociativeTrieNode>>> iter = emptyTrie.allNodeIterator(true);
AssociativeTrieNode>> root = emptyTrie.absoluteRoot();
List extends TrieNode> list = root.getValue();
if(list != null) {
((ArrayList extends TrieNode>) list).trimToSize();
}
while(iter.hasNext()) {
list = iter.next().getValue();
if(list != null) {
((ArrayList extends TrieNode>) list).trimToSize();
}
}
}
TrieNode addNode(OpResult result, TrieNode fromNode, TrieNode