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

inet.ipaddr.format.util.AssociativeAddressTrie Maven / Gradle / Ivy

There is a newer version: 5.5.1
Show newest version
/*
 * 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.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Spliterator;
import java.util.function.Function;
import java.util.function.Supplier;

import inet.ipaddr.Address;
import inet.ipaddr.format.util.AddressTrieOps.AssociativeAddressTriePutOps;
import inet.ipaddr.format.util.BinaryTreeNode.CachingIterator;
import inet.ipaddr.format.util.BinaryTreeNode.ChangeTracker.Change;

/**
 * An address trie in which each node is associated with a value.
 * 

* The trie can also be used as the backing data structure for a {@link AddressTrieMap} which is a {@link java.util.NavigableMap}. * Unlike {@link java.util.TreeMap} this data structure provides access to the nodes and the associated sub-trie with each node as the sub-trie root, * which corresponds with their associated CIDR prefix block subnets. *

* Use one of the put methods to add nodes with values or to change the values of existing nodes. * You can also add to the trie using {@link #add(Address)} and the associated value will be null. *

* Mapped tries are thread-safe when not being modified (ie mappings added or removed), but are not thread-safe when a thread is modifying the trie. *

* To make them thread-safe during addition and removal you could access them through the collection provided by {@link java.util.Collections#synchronizedMap}, * applied to the map from {@link #asMap()} * * @author scfoley * * @param the type of the address keys * @param the type of the associated values */ public abstract class AssociativeAddressTrie extends AddressTrie implements AssociativeAddressTriePutOps { private static final long serialVersionUID = 1L; public static abstract class AssociativeTrieNode extends TrieNode implements Map.Entry, AssociativeAddressTrieOps { private static final long serialVersionUID = 1L; private V value; protected AssociativeTrieNode(K item) { super(item); } @Override public V getValue() { return value; } @Override public V setValue(V value) { V result = getValue(); this.value = value; return result; } public void clearValue() { this.value = null; } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode getUpperSubNode() { return (AssociativeTrieNode) super.getUpperSubNode(); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode getLowerSubNode() { return (AssociativeTrieNode) super.getLowerSubNode(); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode getParent() { return (AssociativeTrieNode) super.getParent(); } @SuppressWarnings("unchecked") @Override public V get(K addr) { AssociativeTrieNode node = (AssociativeTrieNode) doLookup(addr).existingNode; return node == null ? null : node.getValue(); } /** * The hash code is the same as that specified by {@link java.util.Map.Entry#hashCode()} */ @Override public int hashCode() { if(value == null) { return super.hashCode(); } return super.hashCode() ^ value.hashCode(); } @SuppressWarnings("unchecked") @Override public AssociativeAddressTrie asNewTrie() { return (AssociativeAddressTrie) super.asNewTrie(); } /** * Clones the subtrie starting with this node as root. * The nodes are cloned, the keys and values are not cloned. */ @SuppressWarnings("unchecked") @Override public AssociativeTrieNode cloneTree() { return (AssociativeTrieNode) super.cloneTree(); } /** * Clones the node. Keys and values are not cloned, but parent node, lower and upper sub-nodes, * are all set to null. */ @SuppressWarnings("unchecked") @Override public AssociativeTrieNode clone() { return (AssociativeTrieNode) super.clone(); } /** * Returns whether the key and mapped value match those of the given node */ @SuppressWarnings("unchecked") @Override public boolean equals(Object o) { if (o == this) { return true; } else if(o instanceof AssociativeTrieNode) { AssociativeTrieNode other = ((AssociativeTrieNode) o); return super.equals(o) && Objects.equals(getValue(), other.getValue()); } return false; } @SuppressWarnings("unchecked") @Override public Iterator> nodeIterator(boolean forward) { return (Iterator>) super.nodeIterator(forward); } @SuppressWarnings("unchecked") @Override public Iterator> allNodeIterator(boolean forward) { return (Iterator>) super.allNodeIterator(forward); } @SuppressWarnings("unchecked") @Override public Iterator> blockSizeNodeIterator(boolean lowerSubNodeFirst) { return (Iterator>) super.blockSizeNodeIterator(lowerSubNodeFirst); } @SuppressWarnings("unchecked") @Override public Iterator> blockSizeAllNodeIterator(boolean lowerSubNodeFirst) { return (Iterator>) super.blockSizeAllNodeIterator(lowerSubNodeFirst); } @SuppressWarnings("unchecked") @Override public CachingIterator, K, C> blockSizeCachingAllNodeIterator() { return (CachingIterator, K, C>) super.blockSizeCachingAllNodeIterator(); } @SuppressWarnings("unchecked") @Override public CachingIterator, K, C> containingFirstIterator(boolean forwardSubNodeOrder) { return (CachingIterator, K, C>) super.containingFirstIterator(forwardSubNodeOrder); } @SuppressWarnings("unchecked") @Override public CachingIterator, K, C> containingFirstAllNodeIterator(boolean forwardSubNodeOrder) { return (CachingIterator, K, C>) super.containingFirstAllNodeIterator(forwardSubNodeOrder); } @SuppressWarnings("unchecked") @Override public Iterator> containedFirstIterator(boolean forwardSubNodeOrder) { return (Iterator>) super.containedFirstIterator(forwardSubNodeOrder); } @SuppressWarnings("unchecked") @Override public Iterator> containedFirstAllNodeIterator(boolean forwardSubNodeOrder) { return (Iterator>) super.containedFirstAllNodeIterator(forwardSubNodeOrder); } @Override public Spliterator> nodeSpliterator(boolean forward) { return nodeSpliterator(forward, true); } @Override public Spliterator> allNodeSpliterator(boolean forward) { return nodeSpliterator(forward, false); } @Override @SuppressWarnings("unchecked") Spliterator> nodeSpliterator(boolean forward, boolean addedNodesOnly) { return (Spliterator>) super.nodeSpliterator(forward, addedNodesOnly); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode previousAddedNode() { return (AssociativeTrieNode) super.previousAddedNode(); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode nextAddedNode() { return (AssociativeTrieNode) super.nextAddedNode(); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode nextNode() { return (AssociativeTrieNode) super.nextNode(); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode previousNode() { return (AssociativeTrieNode) super.previousNode(); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode firstNode() { return (AssociativeTrieNode) super.firstNode(); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode lastNode() { return (AssociativeTrieNode) super.lastNode(); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode firstAddedNode() { return (AssociativeTrieNode) super.firstAddedNode(); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode lastAddedNode() { return (AssociativeTrieNode) super.lastAddedNode(); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode lowerAddedNode(K addr) { return (AssociativeTrieNode) super.lowerAddedNode(addr); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode floorAddedNode(K addr) { return (AssociativeTrieNode) super.floorAddedNode(addr); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode higherAddedNode(K addr) { return (AssociativeTrieNode) super.higherAddedNode(addr); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode ceilingAddedNode(K addr) { return (AssociativeTrieNode) super.ceilingAddedNode(addr); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode getAddedNode(K addr) { return (AssociativeTrieNode) super.getAddedNode(addr); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode getNode(K addr) { return (AssociativeTrieNode) super.getNode(addr); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode removeElementsContainedBy(K addr) { return (AssociativeTrieNode) super.removeElementsContainedBy(addr); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode elementsContainedBy(K addr) { return (AssociativeTrieNode) super.elementsContainedBy(addr); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode elementsContaining(K addr) { return (AssociativeTrieNode) super.elementsContaining(addr); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode longestPrefixMatchNode(K addr) { return (AssociativeTrieNode) super.longestPrefixMatchNode(addr); } @Override @SuppressWarnings("unchecked") void matchedInserted(OpResult result) { super.matchedInserted(result); result.existingValue = getValue(); setValue((V) result.newValue); } @Override @SuppressWarnings("unchecked") void added(OpResult result) { super.added(result); setValue((V) result.newValue); } /** * * @param result * @return true if a new node needs to be created (match is null) or added (match is non-null) */ @Override @SuppressWarnings("unchecked") boolean remap(OpResult result, boolean isMatch) { Function remapper = (Function) result.remapper; Object newValue; Change change = changeTracker.getCurrent(); V existingValue = isMatch ? getValue() : null; result.existingValue = existingValue; newValue = remapper.apply(existingValue); if(newValue == REMAP_ACTION.DO_NOTHING) { return false; } else if(newValue == REMAP_ACTION.REMOVE_NODE) { if(isMatch) { changeTracker.changedSince(change); clearValue(); removeOp(result); } return false; } else if (isMatch) { if(newValue != existingValue) { changeTracker.changedSince(change); result.newValue = newValue; return true; } // else node already has the value we want return false; } else { result.newValue = newValue; return true; } } /** * The node remains in the trie, but is no longer an added node. * Even if the node is removed from the trie, we must remove the value, * this is needed for the compute method, which returns the value (which must be null when we have removed). */ @Override void removed() { super.removed(); clearValue(); } @SuppressWarnings("unchecked") @Override protected void replaceThisRoot(BinaryTreeNode replacement) { super.replaceThisRoot(replacement); if(replacement == null) { setValue(null); } else { setValue(((AssociativeTrieNode) replacement).getValue()); } } @Override public String toString() { return toNodeString(new StringBuilder(80), isAdded(), getKey(), getValue()).toString(); } } static enum REMAP_ACTION { DO_NOTHING, REMOVE_NODE } AddressTrieMap map; public AssociativeAddressTrie(AssociativeTrieNode root) { super(root); } protected AssociativeAddressTrie(AssociativeTrieNode root, AddressBounds bounds) { super(root, bounds); } @SuppressWarnings("unchecked") @Override protected AssociativeTrieNode absoluteRoot() { return (AssociativeTrieNode) super.absoluteRoot(); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode getRoot() { return (AssociativeTrieNode) super.getRoot(); } @SuppressWarnings("unchecked") @Override public V put(K addr, V value) { addr = checkBlockOrAddress(addr, true); if(bounds != null) { if(!bounds.isInBounds(addr)) { throwOutOfBounds(); } } adjustRoot(addr); AssociativeTrieNode root = absoluteRoot(); OpResult result = new OpResult<>(addr, Operation.INSERT); result.newValue = value; root.matchBits(result); return (V) result.existingValue; } @Override public boolean putNew(K addr, V value) { addr = checkBlockOrAddress(addr, true); if(bounds != null) { if(!bounds.isInBounds(addr)) { throwOutOfBounds(); } } adjustRoot(addr); AssociativeTrieNode root = absoluteRoot(); OpResult result = new OpResult<>(addr, Operation.INSERT); result.newValue = value; root.matchBits(result); return !result.exists; } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode addNode(K addr) { return (AssociativeTrieNode) super.addNode(addr); } @SuppressWarnings("unchecked") @Override TrieNode addNode(OpResult result, TrieNode fromNode, TrieNode nodeToAdd, boolean withValues) { if(withValues && nodeToAdd instanceof AssociativeTrieNode) { AssociativeTrieNode node = (AssociativeTrieNode) nodeToAdd; result.newValue = node.getValue(); } return super.addNode(result, fromNode, nodeToAdd, withValues); } @Override public abstract AssociativeAddedTree constructAddedNodesTree(); protected static class SubNodesMappingAssociative extends SubNodesMapping> { V value; @Override Object getUnderlyingValue() { return value; } } /** * Constructs a trie in which added nodes are mapped to their list of added sub-nodes. * This trie provides an alternative non-binary tree structure of the added nodes. * It is used by 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 */ protected void contructAssociativeAddedTree(AssociativeAddressTrie> emptyTrie) { emptyTrie.addTrie(absoluteRoot()); // does not add values CachingIterator>, K, AssociativeTrieNode>> cachingIterator = emptyTrie.containingFirstAllNodeIterator(true); Iterator> thisIterator = containingFirstAllNodeIterator(true); while(cachingIterator.hasNext()) { AssociativeTrieNode> newNext = cachingIterator.next(), parent; AssociativeTrieNode thisNext = thisIterator.next(); SubNodesMappingAssociative mapping = new SubNodesMappingAssociative(); mapping.value = thisNext.getValue(); // populate the values from the original trie into the new trie newNext.setValue(mapping); // cache this node with its sub-nodes cachingIterator.cacheWithLowerSubNode(newNext); cachingIterator.cacheWithUpperSubNode(newNext); // the cached object is our parent if(newNext.isAdded()) { parent = cachingIterator.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 SubNodesMappingAssociative mappedNodes = parent.getValue(); ArrayList>> addedSubs = mappedNodes.subNodes; if(addedSubs == null) { addedSubs = new ArrayList>>(newNext.size() - 1); mappedNodes.subNodes = addedSubs; } addedSubs.add(newNext); } // else root } } SubNodesMappingAssociative value = emptyTrie.getRoot().getValue(); if(value != null && value.subNodes != null) { value.subNodes.trimToSize(); } Iterator>> iter = emptyTrie.allNodeIterator(true); while(iter.hasNext()) { SubNodesMappingAssociative list = iter.next().getValue(); if(list != null && list.subNodes != null) { list.subNodes.trimToSize(); } } } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode putTrie(AssociativeTrieNode trie) { return (AssociativeTrieNode) addTrie(trie, true); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode putNode(K addr, V value) { addr = checkBlockOrAddress(addr, true); if(bounds != null) { if(!bounds.isInBounds(addr)) { throwOutOfBounds(); } } adjustRoot(addr); AssociativeTrieNode root = absoluteRoot(); OpResult result = new OpResult<>(addr, Operation.INSERT); result.newValue = value; root.matchBits(result); TrieNode node = result.existingNode; if(node == null) { node = result.inserted; } return (AssociativeTrieNode) node; } @Override public AssociativeTrieNode remap(K addr, Function remapper) { return remapImpl(addr, existingAddr -> { V result = remapper.apply(existingAddr); return result == null ? REMAP_ACTION.REMOVE_NODE : result; }); } @Override public AssociativeTrieNode remapIfAbsent(K addr, Supplier remapper, boolean insertNull) { return remapImpl(addr, existingVal -> { if(existingVal == null) { V result = remapper.get(); if(result != null || insertNull) { return result; } } return REMAP_ACTION.DO_NOTHING; }); } @SuppressWarnings("unchecked") private AssociativeTrieNode remapImpl(K addr, Function remapper) { addr = checkBlockOrAddress(addr, true); AssociativeTrieNode subRoot; if(bounds != null) { if(!bounds.isInBounds(addr)) { throwOutOfBounds(); } subRoot = getRoot(); if(subRoot == null) { subRoot = absoluteRoot(); } } else { subRoot = absoluteRoot(); } OpResult result = new OpResult<>(addr, Operation.REMAP); result.remapper = remapper; subRoot.matchBits(result); TrieNode node = result.existingNode; if(node == null) { node = result.inserted; } return (AssociativeTrieNode) node; } @Override public V get(K addr) { AssociativeTrieNode subRoot; if(bounds != null) { addr = checkBlockOrAddress(addr, true); if(!bounds.isInBounds(addr)) { return null; } subRoot = getRoot(); if(subRoot == null) { return null; } } else { subRoot = absoluteRoot(); } return subRoot.get(addr); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode getAddedNode(K addr) { return (AssociativeTrieNode) super.getAddedNode(addr); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode getNode(K addr) { return (AssociativeTrieNode) super.getNode(addr); } @SuppressWarnings("unchecked") @Override AssociativeTrieNode smallestElementContainingBounds(K addr) { return (AssociativeTrieNode) super.smallestElementContainingBounds(addr); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode removeElementsContainedBy(K addr) { return (AssociativeTrieNode) super.removeElementsContainedBy(addr); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode elementsContainedBy(K addr) { return (AssociativeTrieNode) super.elementsContainedBy(addr); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode elementsContaining(K addr) { return (AssociativeTrieNode) super.elementsContaining(addr); } @SuppressWarnings("unchecked") @Override public AssociativeTrieNode longestPrefixMatchNode(K addr) { return (AssociativeTrieNode) super.longestPrefixMatchNode(addr); } /** * Returns a java.util.NavigableMap backed by this associative trie. * The added elements of this trie are keys for the map, the associated values are the map values. * * @return */ public AddressTrieMap asMap() { AddressTrieMap map = this.map; if(map == null) { map = new AddressTrieMap(this); } return map; } @Override @SuppressWarnings("unchecked") AssociativeAddressTrie elementsContainedByToSubTrie(K addr) { return (AssociativeAddressTrie) super.elementsContainedByToSubTrie(addr); } @Override @SuppressWarnings("unchecked") AssociativeAddressTrie elementsContainingToTrie(K addr) { return (AssociativeAddressTrie) super.elementsContainingToTrie(addr); } // creates a new one-node trie with a new root and the given bounds @Override protected abstract AssociativeAddressTrie createNew(AddressBounds bounds); // create a trie with the same root as this one, but different bounds @Override protected abstract AssociativeAddressTrie createSubTrie(AddressBounds bounds); @SuppressWarnings("unchecked") @Override public Iterator> nodeIterator(boolean forward) { return (Iterator>) super.nodeIterator(forward); } @SuppressWarnings("unchecked") @Override public Iterator> allNodeIterator(boolean forward) { return (Iterator>) super.allNodeIterator(forward); } @SuppressWarnings("unchecked") @Override public CachingIterator, K, C> blockSizeCachingAllNodeIterator() { return (CachingIterator, K, C>) super.blockSizeCachingAllNodeIterator(); } @SuppressWarnings("unchecked") @Override public Iterator> blockSizeNodeIterator(boolean lowerSubNodeFirst) { return (Iterator>) super.blockSizeNodeIterator(lowerSubNodeFirst); } @SuppressWarnings("unchecked") @Override public Iterator> blockSizeAllNodeIterator(boolean lowerSubNodeFirst) { return (Iterator>) super.blockSizeAllNodeIterator(lowerSubNodeFirst); } @SuppressWarnings("unchecked") @Override public CachingIterator, K, C> containingFirstIterator(boolean lowerSubNodeFirst) { return (CachingIterator, K, C>) super.containingFirstIterator(lowerSubNodeFirst); } @SuppressWarnings("unchecked") @Override public CachingIterator, K, C> containingFirstAllNodeIterator(boolean lowerSubNodeFirst) { return (CachingIterator, K, C>) super.containingFirstAllNodeIterator(lowerSubNodeFirst); } @SuppressWarnings("unchecked") @Override public Iterator> containedFirstIterator(boolean forwardSubNodeOrder) { return (Iterator>) super.containedFirstIterator(forwardSubNodeOrder); } @SuppressWarnings("unchecked") @Override public Iterator> containedFirstAllNodeIterator(boolean forwardSubNodeOrder) { return (Iterator>) super.containedFirstAllNodeIterator(forwardSubNodeOrder); } @SuppressWarnings("unchecked") @Override Spliterator> nodeSpliterator(boolean forward, boolean addedNodesOnly) { return (Spliterator>) super.nodeSpliterator(forward, addedNodesOnly); } @Override public Spliterator> nodeSpliterator(boolean forward) { return nodeSpliterator(forward, true); } @Override public Spliterator> allNodeSpliterator(boolean forward) { return nodeSpliterator(forward, false); } @Override @SuppressWarnings("unchecked") public AssociativeTrieNode firstNode() { return (AssociativeTrieNode) super.firstNode(); } @Override @SuppressWarnings("unchecked") public AssociativeTrieNode lastNode() { return (AssociativeTrieNode) super.lastNode(); } @Override @SuppressWarnings("unchecked") public AssociativeTrieNode firstAddedNode() { return (AssociativeTrieNode) super.firstAddedNode(); } @Override @SuppressWarnings("unchecked") public AssociativeTrieNode lastAddedNode() { return (AssociativeTrieNode) super.lastAddedNode(); } @Override @SuppressWarnings("unchecked") public AssociativeTrieNode lowerAddedNode(K addr) { return (AssociativeTrieNode) super.lowerAddedNode(addr); } @Override @SuppressWarnings("unchecked") public AssociativeTrieNode floorAddedNode(K addr) { return (AssociativeTrieNode) super.floorAddedNode(addr); } @Override @SuppressWarnings("unchecked") public AssociativeTrieNode higherAddedNode(K addr) { return (AssociativeTrieNode) super.higherAddedNode(addr); } @Override @SuppressWarnings("unchecked") public AssociativeTrieNode ceilingAddedNode(K addr) { return (AssociativeTrieNode) super.ceilingAddedNode(addr); } @SuppressWarnings("unchecked") @Override public AssociativeAddressTrie clone() { AssociativeAddressTrie result = (AssociativeAddressTrie) super.clone(); result.map = null; return result; } @Override public boolean equals(Object o) { return o instanceof AssociativeAddressTrie && super.equals(o); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy