From 83226e162de60bd22551b1a37cc3e513811e1449 Mon Sep 17 00:00:00 2001 From: Thomas Neidhart Date: Thu, 13 Jun 2013 21:01:00 +0000 Subject: [PATCH] Refactor trie package: reduce interface by extending IterableSortedMap and only adding prefixMap method, remove all key analyzers but the StringKeyAnalyzer, refactor PatriciaTrie class by moving all remaining methods to AbstractPatriciaTrie and fixing the key type to String, integrating the test classes into the framework. git-svn-id: https://svn.apache.org/repos/asf/commons/proper/collections/trunk@1492866 13f79535-47bb-0310-9956-ffa450edef68 --- .../trie/AbstractBitwiseTrie.java | 117 +- .../trie/AbstractPatriciaTrie.java | 2448 +++++++++++++++++ .../collections4/trie/PatriciaTrie.java | 1208 +------- .../collections4/trie/PatriciaTrieBase.java | 1122 -------- .../collections4/trie/SynchronizedTrie.java | 39 +- .../collections4/trie/UnmodifiableTrie.java | 63 +- .../trie/analyzer/ByteArrayKeyAnalyzer.java | 172 -- .../trie/analyzer/ByteKeyAnalyzer.java | 96 - .../trie/analyzer/CharArrayKeyAnalyzer.java | 137 - .../trie/analyzer/CharacterKeyAnalyzer.java | 98 - .../trie/analyzer/IntegerKeyAnalyzer.java | 97 - .../trie/analyzer/LongKeyAnalyzer.java | 97 - .../trie/analyzer/ShortKeyAnalyzer.java | 103 - .../collections4/trie/PatriciaTrie2Test.java | 58 + .../collections4/trie/PatriciaTrieTest.java | 902 +----- .../trie/UnmodifiableTrieTest.java | 106 + .../analyzer/ByteArrayKeyAnalyzerTest.java | 112 - .../PatriciaTrie.emptyCollection.version4.obj | Bin 0 -> 430 bytes .../PatriciaTrie.fullCollection.version4.obj | Bin 0 -> 693 bytes ...odifiableTrie.emptyCollection.version4.obj | Bin 0 -> 505 bytes ...modifiableTrie.fullCollection.version4.obj | Bin 0 -> 768 bytes 21 files changed, 2727 insertions(+), 4248 deletions(-) create mode 100644 src/main/java/org/apache/commons/collections4/trie/AbstractPatriciaTrie.java delete mode 100644 src/main/java/org/apache/commons/collections4/trie/PatriciaTrieBase.java delete mode 100644 src/main/java/org/apache/commons/collections4/trie/analyzer/ByteArrayKeyAnalyzer.java delete mode 100644 src/main/java/org/apache/commons/collections4/trie/analyzer/ByteKeyAnalyzer.java delete mode 100644 src/main/java/org/apache/commons/collections4/trie/analyzer/CharArrayKeyAnalyzer.java delete mode 100644 src/main/java/org/apache/commons/collections4/trie/analyzer/CharacterKeyAnalyzer.java delete mode 100644 src/main/java/org/apache/commons/collections4/trie/analyzer/IntegerKeyAnalyzer.java delete mode 100644 src/main/java/org/apache/commons/collections4/trie/analyzer/LongKeyAnalyzer.java delete mode 100644 src/main/java/org/apache/commons/collections4/trie/analyzer/ShortKeyAnalyzer.java create mode 100644 src/test/java/org/apache/commons/collections4/trie/PatriciaTrie2Test.java create mode 100644 src/test/java/org/apache/commons/collections4/trie/UnmodifiableTrieTest.java delete mode 100644 src/test/java/org/apache/commons/collections4/trie/analyzer/ByteArrayKeyAnalyzerTest.java create mode 100644 src/test/resources/data/test/PatriciaTrie.emptyCollection.version4.obj create mode 100644 src/test/resources/data/test/PatriciaTrie.fullCollection.version4.obj create mode 100644 src/test/resources/data/test/UnmodifiableTrie.emptyCollection.version4.obj create mode 100644 src/test/resources/data/test/UnmodifiableTrie.fullCollection.version4.obj diff --git a/src/main/java/org/apache/commons/collections4/trie/AbstractBitwiseTrie.java b/src/main/java/org/apache/commons/collections4/trie/AbstractBitwiseTrie.java index fd8cba0d8..79a3be693 100644 --- a/src/main/java/org/apache/commons/collections4/trie/AbstractBitwiseTrie.java +++ b/src/main/java/org/apache/commons/collections4/trie/AbstractBitwiseTrie.java @@ -30,22 +30,22 @@ import org.apache.commons.collections4.Trie; * @since 4.0 * @version $Id$ */ -abstract class AbstractBitwiseTrie extends AbstractMap +public abstract class AbstractBitwiseTrie extends AbstractMap implements Trie, Serializable { private static final long serialVersionUID = 5826987063535505652L; - // TODO Privatise fields? - /** * The {@link KeyAnalyzer} that's being used to build the PATRICIA {@link Trie}. */ - protected final KeyAnalyzer keyAnalyzer; + private final KeyAnalyzer keyAnalyzer; /** * Constructs a new {@link Trie} using the given {@link KeyAnalyzer}. + * + * @param keyAnalyzer the {@link KeyAnalyzer} to use */ - public AbstractBitwiseTrie(final KeyAnalyzer keyAnalyzer) { + protected AbstractBitwiseTrie(final KeyAnalyzer keyAnalyzer) { if (keyAnalyzer == null) { throw new NullPointerException("keyAnalyzer"); } @@ -55,108 +55,12 @@ abstract class AbstractBitwiseTrie extends AbstractMap /** * Returns the {@link KeyAnalyzer} that constructed the {@link Trie}. + * @return the {@link KeyAnalyzer} used by this {@link Trie} */ - public KeyAnalyzer getKeyAnalyzer() { + protected KeyAnalyzer getKeyAnalyzer() { return keyAnalyzer; } - /** - * Returns the {@link Entry} whose key is closest in a bitwise XOR - * metric to the given key. This is NOT lexicographic closeness. - * For example, given the keys: - * - *
    - *
  1. D = 1000100 - *
  2. H = 1001000 - *
  3. L = 1001100 - *
- * - * If the {@link Trie} contained 'H' and 'L', a lookup of 'D' would - * return 'L', because the XOR distance between D & L is smaller - * than the XOR distance between D & H. - * - * @param key the key to use in the search - * @return the {@link Entry} whose key is closest in a bitwise XOR metric - * to the provided key - */ - public abstract Map.Entry select(K key); - - /** - * Returns the key that is closest in a bitwise XOR metric to the - * provided key. This is NOT lexicographic closeness! - * - * For example, given the keys: - * - *
    - *
  1. D = 1000100 - *
  2. H = 1001000 - *
  3. L = 1001100 - *
- * - * If the {@link Trie} contained 'H' and 'L', a lookup of 'D' would - * return 'L', because the XOR distance between D & L is smaller - * than the XOR distance between D & H. - * - * @param key the key to use in the search - * @return the key that is closest in a bitwise XOR metric to the provided key - */ - public K selectKey(final K key) { - final Map.Entry entry = select(key); - if (entry == null) { - return null; - } - return entry.getKey(); - } - - /** - * Returns the value whose key is closest in a bitwise XOR metric to - * the provided key. This is NOT lexicographic closeness! - * - * For example, given the keys: - * - *
    - *
  1. D = 1000100 - *
  2. H = 1001000 - *
  3. L = 1001100 - *
- * - * If the {@link Trie} contained 'H' and 'L', a lookup of 'D' would - * return 'L', because the XOR distance between D & L is smaller - * than the XOR distance between D & H. - * - * @param key the key to use in the search - * @return the value whose key is closest in a bitwise XOR metric - * to the provided key - */ - public V selectValue(final K key) { - final Map.Entry entry = select(key); - if (entry == null) { - return null; - } - return entry.getValue(); - } - - /** - * Iterates through the {@link Trie}, starting with the entry whose bitwise - * value is closest in an XOR metric to the given key. After the closest - * entry is found, the {@link Trie} will call select on that entry and continue - * calling select for each entry (traversing in order of XOR closeness, - * NOT lexicographically) until the cursor returns {@link Cursor.Decision#EXIT}. - *

- * The cursor can return {@link Cursor.Decision#CONTINUE} to continue traversing. - *

- * {@link Cursor.Decision#REMOVE_AND_EXIT} is used to remove the current element - * and stop traversing. - *

- * Note: The {@link Cursor.Decision#REMOVE} operation is not supported. - * - * @param key the key to use in the search - * @param cursor the cursor used throughout the search - * @return the entry the cursor returned {@link Cursor.Decision#EXIT} on, or null - * if it continued till the end - */ - public abstract Map.Entry select(K key, Cursor cursor); - @Override public String toString() { final StringBuilder buffer = new StringBuilder(); @@ -248,17 +152,13 @@ abstract class AbstractBitwiseTrie extends AbstractMap protected V value; - private final int hashCode; - public BasicEntry(final K key) { this.key = key; - this.hashCode = key != null ? key.hashCode() : 0; } public BasicEntry(final K key, final V value) { this.key = key; this.value = value; - this.hashCode = (key != null ? key.hashCode() : 0) ^ (value != null ? value.hashCode() : 0); } /** @@ -285,7 +185,8 @@ abstract class AbstractBitwiseTrie extends AbstractMap @Override public int hashCode() { - return hashCode; + return (getKey() == null ? 0 : getKey().hashCode()) ^ + (getValue() == null ? 0 : getValue().hashCode()); } @Override diff --git a/src/main/java/org/apache/commons/collections4/trie/AbstractPatriciaTrie.java b/src/main/java/org/apache/commons/collections4/trie/AbstractPatriciaTrie.java new file mode 100644 index 000000000..12cf71e07 --- /dev/null +++ b/src/main/java/org/apache/commons/collections4/trie/AbstractPatriciaTrie.java @@ -0,0 +1,2448 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * 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 org.apache.commons.collections4.trie; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.SortedMap; +import java.util.Map.Entry; + +import org.apache.commons.collections4.OrderedMapIterator; + +/** + * This class implements the base PATRICIA algorithm and everything that + * is related to the {@link Map} interface. + * + * @since 4.0 + * @version $Id$ + */ +abstract class AbstractPatriciaTrie extends AbstractBitwiseTrie { + + private static final long serialVersionUID = 5155253417231339498L; + + /** The root node of the {@link Trie}. */ + private transient TrieEntry root = new TrieEntry(null, null, -1); + + /** + * Each of these fields are initialized to contain an instance of the + * appropriate view the first time this view is requested. The views are + * stateless, so there's no reason to create more than one of each. + */ + private transient volatile Set keySet; + private transient volatile Collection values; + private transient volatile Set> entrySet; + + /** The current size of the {@link Trie}. */ + private transient int size = 0; + + /** + * The number of times this {@link Trie} has been modified. + * It's used to detect concurrent modifications and fail-fast the {@link Iterator}s. + */ + protected transient int modCount = 0; + + protected AbstractPatriciaTrie(final KeyAnalyzer keyAnalyzer) { + super(keyAnalyzer); + } + + /** + * Constructs a new {@link org.apache.commons.collections4.Trie Trie} using the given + * {@link KeyAnalyzer} and initializes the {@link org.apache.commons.collections4.Trie Trie} + * with the values from the provided {@link Map}. + */ + protected AbstractPatriciaTrie(final KeyAnalyzer keyAnalyzer, + final Map map) { + super(keyAnalyzer); + putAll(map); + } + + //----------------------------------------------------------------------- + @Override + public void clear() { + root.key = null; + root.bitIndex = -1; + root.value = null; + + root.parent = null; + root.left = root; + root.right = null; + root.predecessor = root; + + size = 0; + incrementModCount(); + } + + @Override + public int size() { + return size; + } + + /** + * A helper method to increment the {@link Trie} size and the modification counter. + */ + void incrementSize() { + size++; + incrementModCount(); + } + + /** + * A helper method to decrement the {@link Trie} size and increment the modification counter. + */ + void decrementSize() { + size--; + incrementModCount(); + } + + /** + * A helper method to increment the modification counter. + */ + private void incrementModCount() { + ++modCount; + } + + @Override + public V put(final K key, final V value) { + if (key == null) { + throw new NullPointerException("Key cannot be null"); + } + + final int lengthInBits = lengthInBits(key); + + // The only place to store a key with a length + // of zero bits is the root node + if (lengthInBits == 0) { + if (root.isEmpty()) { + incrementSize(); + } else { + incrementModCount(); + } + return root.setKeyValue(key, value); + } + + final TrieEntry found = getNearestEntryForKey(key, lengthInBits); + if (compareKeys(key, found.key)) { + if (found.isEmpty()) { // <- must be the root + incrementSize(); + } else { + incrementModCount(); + } + return found.setKeyValue(key, value); + } + + final int bitIndex = bitIndex(key, found.key); + if (!KeyAnalyzer.isOutOfBoundsIndex(bitIndex)) { + if (KeyAnalyzer.isValidBitIndex(bitIndex)) { // in 99.999...9% the case + /* NEW KEY+VALUE TUPLE */ + final TrieEntry t = new TrieEntry(key, value, bitIndex); + addEntry(t, lengthInBits); + incrementSize(); + return null; + } else if (KeyAnalyzer.isNullBitKey(bitIndex)) { + // A bits of the Key are zero. The only place to + // store such a Key is the root Node! + + /* NULL BIT KEY */ + if (root.isEmpty()) { + incrementSize(); + } else { + incrementModCount(); + } + return root.setKeyValue(key, value); + + } else if (KeyAnalyzer.isEqualBitKey(bitIndex)) { + // This is a very special and rare case. + + /* REPLACE OLD KEY+VALUE */ + if (found != root) { + incrementModCount(); + return found.setKeyValue(key, value); + } + } + } + + throw new IllegalArgumentException("Failed to put: " + key + " -> " + value + ", " + bitIndex); + } + + /** + * Adds the given {@link TrieEntry} to the {@link Trie}. + */ + TrieEntry addEntry(final TrieEntry entry, final int lengthInBits) { + TrieEntry current = root.left; + TrieEntry path = root; + while(true) { + if (current.bitIndex >= entry.bitIndex + || current.bitIndex <= path.bitIndex) { + entry.predecessor = entry; + + if (!isBitSet(entry.key, entry.bitIndex, lengthInBits)) { + entry.left = entry; + entry.right = current; + } else { + entry.left = current; + entry.right = entry; + } + + entry.parent = path; + if (current.bitIndex >= entry.bitIndex) { + current.parent = entry; + } + + // if we inserted an uplink, set the predecessor on it + if (current.bitIndex <= path.bitIndex) { + current.predecessor = entry; + } + + if (path == root || !isBitSet(entry.key, path.bitIndex, lengthInBits)) { + path.left = entry; + } else { + path.right = entry; + } + + return entry; + } + + path = current; + + if (!isBitSet(entry.key, current.bitIndex, lengthInBits)) { + current = current.left; + } else { + current = current.right; + } + } + } + + @Override + public V get(final Object k) { + final TrieEntry entry = getEntry(k); + return entry != null ? entry.getValue() : null; + } + + /** + * Returns the entry associated with the specified key in the + * PatriciaTrieBase. Returns null if the map contains no mapping + * for this key. + *

+ * This may throw ClassCastException if the object is not of type K. + */ + TrieEntry getEntry(final Object k) { + final K key = castKey(k); + if (key == null) { + return null; + } + + final int lengthInBits = lengthInBits(key); + final TrieEntry entry = getNearestEntryForKey(key, lengthInBits); + return !entry.isEmpty() && compareKeys(key, entry.key) ? entry : null; + } + + /** + * Returns the {@link Entry} whose key is closest in a bitwise XOR + * metric to the given key. This is NOT lexicographic closeness. + * For example, given the keys: + * + *

    + *
  1. D = 1000100 + *
  2. H = 1001000 + *
  3. L = 1001100 + *
+ * + * If the {@link Trie} contained 'H' and 'L', a lookup of 'D' would + * return 'L', because the XOR distance between D & L is smaller + * than the XOR distance between D & H. + * + * @param key the key to use in the search + * @return the {@link Entry} whose key is closest in a bitwise XOR metric + * to the provided key + */ + public Map.Entry select(final K key) { + final int lengthInBits = lengthInBits(key); + final Reference> reference = new Reference>(); + if (!selectR(root.left, -1, key, lengthInBits, reference)) { + return reference.get(); + } + return null; + } + + /** + * Returns the key that is closest in a bitwise XOR metric to the + * provided key. This is NOT lexicographic closeness! + * + * For example, given the keys: + * + *
    + *
  1. D = 1000100 + *
  2. H = 1001000 + *
  3. L = 1001100 + *
+ * + * If the {@link Trie} contained 'H' and 'L', a lookup of 'D' would + * return 'L', because the XOR distance between D & L is smaller + * than the XOR distance between D & H. + * + * @param key the key to use in the search + * @return the key that is closest in a bitwise XOR metric to the provided key + */ + public K selectKey(final K key) { + final Map.Entry entry = select(key); + if (entry == null) { + return null; + } + return entry.getKey(); + } + + /** + * Returns the value whose key is closest in a bitwise XOR metric to + * the provided key. This is NOT lexicographic closeness! + * + * For example, given the keys: + * + *
    + *
  1. D = 1000100 + *
  2. H = 1001000 + *
  3. L = 1001100 + *
+ * + * If the {@link Trie} contained 'H' and 'L', a lookup of 'D' would + * return 'L', because the XOR distance between D & L is smaller + * than the XOR distance between D & H. + * + * @param key the key to use in the search + * @return the value whose key is closest in a bitwise XOR metric + * to the provided key + */ + public V selectValue(final K key) { + final Map.Entry entry = select(key); + if (entry == null) { + return null; + } + return entry.getValue(); + } + + /** + * This is equivalent to the other {@link #selectR(TrieEntry, int, Object, int, Cursor, Reference)} + * method but without its overhead because we're selecting only one best matching Entry from the {@link Trie}. + */ + private boolean selectR(final TrieEntry h, final int bitIndex, + final K key, final int lengthInBits, + final Reference> reference) { + + if (h.bitIndex <= bitIndex) { + // If we hit the root Node and it is empty + // we have to look for an alternative best + // matching node. + if (!h.isEmpty()) { + reference.set(h); + return false; + } + return true; + } + + if (!isBitSet(key, h.bitIndex, lengthInBits)) { + if (selectR(h.left, h.bitIndex, key, lengthInBits, reference)) { + return selectR(h.right, h.bitIndex, key, lengthInBits, reference); + } + } else { + if (selectR(h.right, h.bitIndex, key, lengthInBits, reference)) { + return selectR(h.left, h.bitIndex, key, lengthInBits, reference); + } + } + return false; + } + + @Override + public boolean containsKey(final Object k) { + if (k == null) { + return false; + } + + final K key = castKey(k); + final int lengthInBits = lengthInBits(key); + final TrieEntry entry = getNearestEntryForKey(key, lengthInBits); + return !entry.isEmpty() && compareKeys(key, entry.key); + } + + @Override + public Set> entrySet() { + if (entrySet == null) { + entrySet = new EntrySet(); + } + return entrySet; + } + + @Override + public Set keySet() { + if (keySet == null) { + keySet = new KeySet(); + } + return keySet; + } + + @Override + public Collection values() { + if (values == null) { + values = new Values(); + } + return values; + } + + /** + * {@inheritDoc} + * + * @throws ClassCastException if provided key is of an incompatible type + */ + @Override + public V remove(final Object k) { + if (k == null) { + return null; + } + + final K key = castKey(k); + final int lengthInBits = lengthInBits(key); + TrieEntry current = root.left; + TrieEntry path = root; + while (true) { + if (current.bitIndex <= path.bitIndex) { + if (!current.isEmpty() && compareKeys(key, current.key)) { + return removeEntry(current); + } else { + return null; + } + } + + path = current; + + if (!isBitSet(key, current.bitIndex, lengthInBits)) { + current = current.left; + } else { + current = current.right; + } + } + } + + /** + * Returns the nearest entry for a given key. This is useful + * for finding knowing if a given key exists (and finding the value + * for it), or for inserting the key. + * + * The actual get implementation. This is very similar to + * selectR but with the exception that it might return the + * root Entry even if it's empty. + */ + TrieEntry getNearestEntryForKey(final K key, final int lengthInBits) { + TrieEntry current = root.left; + TrieEntry path = root; + while(true) { + if (current.bitIndex <= path.bitIndex) { + return current; + } + + path = current; + if (!isBitSet(key, current.bitIndex, lengthInBits)) { + current = current.left; + } else { + current = current.right; + } + } + } + + /** + * Removes a single entry from the {@link Trie}. + * + * If we found a Key (Entry h) then figure out if it's + * an internal (hard to remove) or external Entry (easy + * to remove) + */ + V removeEntry(final TrieEntry h) { + if (h != root) { + if (h.isInternalNode()) { + removeInternalEntry(h); + } else { + removeExternalEntry(h); + } + } + + decrementSize(); + return h.setKeyValue(null, null); + } + + /** + * Removes an external entry from the {@link Trie}. + * + * If it's an external Entry then just remove it. + * This is very easy and straight forward. + */ + private void removeExternalEntry(final TrieEntry h) { + if (h == root) { + throw new IllegalArgumentException("Cannot delete root Entry!"); + } else if (!h.isExternalNode()) { + throw new IllegalArgumentException(h + " is not an external Entry!"); + } + + final TrieEntry parent = h.parent; + final TrieEntry child = h.left == h ? h.right : h.left; + + if (parent.left == h) { + parent.left = child; + } else { + parent.right = child; + } + + // either the parent is changing, or the predecessor is changing. + if (child.bitIndex > parent.bitIndex) { + child.parent = parent; + } else { + child.predecessor = parent; + } + + } + + /** + * Removes an internal entry from the {@link Trie}. + * + * If it's an internal Entry then "good luck" with understanding + * this code. The Idea is essentially that Entry p takes Entry h's + * place in the trie which requires some re-wiring. + */ + private void removeInternalEntry(final TrieEntry h) { + if (h == root) { + throw new IllegalArgumentException("Cannot delete root Entry!"); + } else if (!h.isInternalNode()) { + throw new IllegalArgumentException(h + " is not an internal Entry!"); + } + + final TrieEntry p = h.predecessor; + + // Set P's bitIndex + p.bitIndex = h.bitIndex; + + // Fix P's parent, predecessor and child Nodes + { + final TrieEntry parent = p.parent; + final TrieEntry child = p.left == h ? p.right : p.left; + + // if it was looping to itself previously, + // it will now be pointed from it's parent + // (if we aren't removing it's parent -- + // in that case, it remains looping to itself). + // otherwise, it will continue to have the same + // predecessor. + if (p.predecessor == p && p.parent != h) { + p.predecessor = p.parent; + } + + if (parent.left == p) { + parent.left = child; + } else { + parent.right = child; + } + + if (child.bitIndex > parent.bitIndex) { + child.parent = parent; + } + } + + // Fix H's parent and child Nodes + { + // If H is a parent of its left and right child + // then change them to P + if (h.left.parent == h) { + h.left.parent = p; + } + + if (h.right.parent == h) { + h.right.parent = p; + } + + // Change H's parent + if (h.parent.left == h) { + h.parent.left = p; + } else { + h.parent.right = p; + } + } + + // Copy the remaining fields from H to P + //p.bitIndex = h.bitIndex; + p.parent = h.parent; + p.left = h.left; + p.right = h.right; + + // Make sure that if h was pointing to any uplinks, + // p now points to them. + if (isValidUplink(p.left, p)) { + p.left.predecessor = p; + } + + if (isValidUplink(p.right, p)) { + p.right.predecessor = p; + } + } + + /** + * Returns the entry lexicographically after the given entry. + * If the given entry is null, returns the first node. + */ + TrieEntry nextEntry(final TrieEntry node) { + if (node == null) { + return firstEntry(); + } else { + return nextEntryImpl(node.predecessor, node, null); + } + } + + /** + * Scans for the next node, starting at the specified point, and using 'previous' + * as a hint that the last node we returned was 'previous' (so we know not to return + * it again). If 'tree' is non-null, this will limit the search to the given tree. + * + * The basic premise is that each iteration can follow the following steps: + * + * 1) Scan all the way to the left. + * a) If we already started from this node last time, proceed to Step 2. + * b) If a valid uplink is found, use it. + * c) If the result is an empty node (root not set), break the scan. + * d) If we already returned the left node, break the scan. + * + * 2) Check the right. + * a) If we already returned the right node, proceed to Step 3. + * b) If it is a valid uplink, use it. + * c) Do Step 1 from the right node. + * + * 3) Back up through the parents until we encounter find a parent + * that we're not the right child of. + * + * 4) If there's no right child of that parent, the iteration is finished. + * Otherwise continue to Step 5. + * + * 5) Check to see if the right child is a valid uplink. + * a) If we already returned that child, proceed to Step 6. + * Otherwise, use it. + * + * 6) If the right child of the parent is the parent itself, we've + * already found & returned the end of the Trie, so exit. + * + * 7) Do Step 1 on the parent's right child. + */ + TrieEntry nextEntryImpl(final TrieEntry start, + final TrieEntry previous, final TrieEntry tree) { + + TrieEntry current = start; + + // Only look at the left if this was a recursive or + // the first check, otherwise we know we've already looked + // at the left. + if (previous == null || start != previous.predecessor) { + while (!current.left.isEmpty()) { + // stop traversing if we've already + // returned the left of this node. + if (previous == current.left) { + break; + } + + if (isValidUplink(current.left, current)) { + return current.left; + } + + current = current.left; + } + } + + // If there's no data at all, exit. + if (current.isEmpty()) { + return null; + } + + // If we've already returned the left, + // and the immediate right is null, + // there's only one entry in the Trie + // which is stored at the root. + // + // / ("") <-- root + // \_/ \ + // null <-- 'current' + // + if (current.right == null) { + return null; + } + + // If nothing valid on the left, try the right. + if (previous != current.right) { + // See if it immediately is valid. + if (isValidUplink(current.right, current)) { + return current.right; + } + + // Must search on the right's side if it wasn't initially valid. + return nextEntryImpl(current.right, previous, tree); + } + + // Neither left nor right are valid, find the first parent + // whose child did not come from the right & traverse it. + while (current == current.parent.right) { + // If we're going to traverse to above the subtree, stop. + if (current == tree) { + return null; + } + + current = current.parent; + } + + // If we're on the top of the subtree, we can't go any higher. + if (current == tree) { + return null; + } + + // If there's no right, the parent must be root, so we're done. + if (current.parent.right == null) { + return null; + } + + // If the parent's right points to itself, we've found one. + if (previous != current.parent.right + && isValidUplink(current.parent.right, current.parent)) { + return current.parent.right; + } + + // If the parent's right is itself, there can't be any more nodes. + if (current.parent.right == current.parent) { + return null; + } + + // We need to traverse down the parent's right's path. + return nextEntryImpl(current.parent.right, previous, tree); + } + + /** + * Returns the first entry the {@link Trie} is storing. + *

+ * This is implemented by going always to the left until + * we encounter a valid uplink. That uplink is the first key. + */ + TrieEntry firstEntry() { + // if Trie is empty, no first node. + if (isEmpty()) { + return null; + } + + return followLeft(root); + } + + /** + * Goes left through the tree until it finds a valid node. + */ + TrieEntry followLeft(TrieEntry node) { + while(true) { + TrieEntry child = node.left; + // if we hit root and it didn't have a node, go right instead. + if (child.isEmpty()) { + child = node.right; + } + + if (child.bitIndex <= node.bitIndex) { + return child; + } + + node = child; + } + } + + /** + * Gets the standard Map hashCode. + * + * @return the hash code defined in the Map interface + */ + @Override + public int hashCode() { + int total = 0; + final Iterator> it = new MyEntryIterator(); + + while (it.hasNext()) { + total += it.next().hashCode(); + } + return total; + } + + private class MyEntryIterator extends TrieIterator> { + public Map.Entry next() { + return nextEntry(); + } + } + + //----------------------------------------------------------------------- + + public Comparator comparator() { + return getKeyAnalyzer(); + } + + public K firstKey() { + if (size() == 0) { + throw new NoSuchElementException(); + } else { + return firstEntry().getKey(); + } + } + + public K lastKey() { + final TrieEntry entry = lastEntry(); + if (entry != null) { + return entry.getKey(); + } else { + throw new NoSuchElementException(); + } + } + + public K nextKey(final K key) { + if (key == null) { + throw new NullPointerException(); + } + final TrieEntry entry = getEntry(key); + if (entry != null) { + final TrieEntry nextEntry = nextEntry(entry); + return nextEntry != null ? nextEntry.getKey() : null; + } else { + return null; + } + } + + public K previousKey(final K key) { + if (key == null) { + throw new NullPointerException(); + } + final TrieEntry entry = getEntry(key); + if (entry != null) { + final TrieEntry prevEntry = previousEntry(entry); + return prevEntry != null ? prevEntry.getKey() : null; + } else { + return null; + } + } + + public OrderedMapIterator mapIterator() { + return new TrieMapIterator(); + } + + public SortedMap prefixMap(final K key) { + return getPrefixMapByBits(key, 0, lengthInBits(key)); + } + + /** + * Returns a view of this {@link Trie} of all elements that are prefixed + * by the number of bits in the given Key. + *

+ * The view that this returns is optimized to have a very efficient + * {@link Iterator}. The {@link SortedMap#firstKey()}, + * {@link SortedMap#lastKey()} & {@link Map#size()} methods must + * iterate over all possible values in order to determine the results. + * This information is cached until the PATRICIA {@link Trie} changes. + * All other methods (except {@link Iterator}) must compare the given + * key to the prefix to ensure that it is within the range of the view. + * The {@link Iterator}'s remove method must also relocate the subtree + * that contains the prefixes if the entry holding the subtree is + * removed or changes. Changing the subtree takes O(K) time. + * + * @param key the key to use in the search + * @param offsetInBits the prefix offset + * @param lengthInBits the number of significant prefix bits + * @return a {@link SortedMap} view of this {@link Trie} with all elements whose + * key is prefixed by the search key + */ + private SortedMap getPrefixMapByBits(final K key, final int offsetInBits, final int lengthInBits) { + + final int offsetLength = offsetInBits + lengthInBits; + if (offsetLength > lengthInBits(key)) { + throw new IllegalArgumentException(offsetInBits + " + " + + lengthInBits + " > " + lengthInBits(key)); + } + + if (offsetLength == 0) { + return this; + } + + return new PrefixRangeMap(key, offsetInBits, lengthInBits); + } + + public SortedMap headMap(final K toKey) { + return new RangeEntryMap(null, toKey); + } + + public SortedMap subMap(final K fromKey, final K toKey) { + return new RangeEntryMap(fromKey, toKey); + } + + public SortedMap tailMap(final K fromKey) { + return new RangeEntryMap(fromKey, null); + } + + /** + * Returns an entry strictly higher than the given key, + * or null if no such entry exists. + */ + TrieEntry higherEntry(final K key) { + // TODO: Cleanup so that we don't actually have to add/remove from the + // tree. (We do it here because there are other well-defined + // functions to perform the search.) + final int lengthInBits = lengthInBits(key); + + if (lengthInBits == 0) { + if (!root.isEmpty()) { + // If data in root, and more after -- return it. + if (size() > 1) { + return nextEntry(root); + } else { // If no more after, no higher entry. + return null; + } + } else { + // Root is empty & we want something after empty, return first. + return firstEntry(); + } + } + + final TrieEntry found = getNearestEntryForKey(key, lengthInBits); + if (compareKeys(key, found.key)) { + return nextEntry(found); + } + + final int bitIndex = bitIndex(key, found.key); + if (KeyAnalyzer.isValidBitIndex(bitIndex)) { + final TrieEntry added = new TrieEntry(key, null, bitIndex); + addEntry(added, lengthInBits); + incrementSize(); // must increment because remove will decrement + final TrieEntry ceil = nextEntry(added); + removeEntry(added); + modCount -= 2; // we didn't really modify it. + return ceil; + } else if (KeyAnalyzer.isNullBitKey(bitIndex)) { + if (!root.isEmpty()) { + return firstEntry(); + } else if (size() > 1) { + return nextEntry(firstEntry()); + } else { + return null; + } + } else if (KeyAnalyzer.isEqualBitKey(bitIndex)) { + return nextEntry(found); + } + + // we should have exited above. + throw new IllegalStateException("invalid lookup: " + key); + } + + /** + * Returns a key-value mapping associated with the least key greater + * than or equal to the given key, or null if there is no such key. + */ + TrieEntry ceilingEntry(final K key) { + // Basically: + // Follow the steps of adding an entry, but instead... + // + // - If we ever encounter a situation where we found an equal + // key, we return it immediately. + // + // - If we hit an empty root, return the first iterable item. + // + // - If we have to add a new item, we temporarily add it, + // find the successor to it, then remove the added item. + // + // These steps ensure that the returned value is either the + // entry for the key itself, or the first entry directly after + // the key. + + // TODO: Cleanup so that we don't actually have to add/remove from the + // tree. (We do it here because there are other well-defined + // functions to perform the search.) + final int lengthInBits = lengthInBits(key); + + if (lengthInBits == 0) { + if (!root.isEmpty()) { + return root; + } else { + return firstEntry(); + } + } + + final TrieEntry found = getNearestEntryForKey(key, lengthInBits); + if (compareKeys(key, found.key)) { + return found; + } + + final int bitIndex = bitIndex(key, found.key); + if (KeyAnalyzer.isValidBitIndex(bitIndex)) { + final TrieEntry added = new TrieEntry(key, null, bitIndex); + addEntry(added, lengthInBits); + incrementSize(); // must increment because remove will decrement + final TrieEntry ceil = nextEntry(added); + removeEntry(added); + modCount -= 2; // we didn't really modify it. + return ceil; + } else if (KeyAnalyzer.isNullBitKey(bitIndex)) { + if (!root.isEmpty()) { + return root; + } else { + return firstEntry(); + } + } else if (KeyAnalyzer.isEqualBitKey(bitIndex)) { + return found; + } + + // we should have exited above. + throw new IllegalStateException("invalid lookup: " + key); + } + + /** + * Returns a key-value mapping associated with the greatest key + * strictly less than the given key, or null if there is no such key. + */ + TrieEntry lowerEntry(final K key) { + // Basically: + // Follow the steps of adding an entry, but instead... + // + // - If we ever encounter a situation where we found an equal + // key, we return it's previousEntry immediately. + // + // - If we hit root (empty or not), return null. + // + // - If we have to add a new item, we temporarily add it, + // find the previousEntry to it, then remove the added item. + // + // These steps ensure that the returned value is always just before + // the key or null (if there was nothing before it). + + // TODO: Cleanup so that we don't actually have to add/remove from the + // tree. (We do it here because there are other well-defined + // functions to perform the search.) + final int lengthInBits = lengthInBits(key); + + if (lengthInBits == 0) { + return null; // there can never be anything before root. + } + + final TrieEntry found = getNearestEntryForKey(key, lengthInBits); + if (compareKeys(key, found.key)) { + return previousEntry(found); + } + + final int bitIndex = bitIndex(key, found.key); + if (KeyAnalyzer.isValidBitIndex(bitIndex)) { + final TrieEntry added = new TrieEntry(key, null, bitIndex); + addEntry(added, lengthInBits); + incrementSize(); // must increment because remove will decrement + final TrieEntry prior = previousEntry(added); + removeEntry(added); + modCount -= 2; // we didn't really modify it. + return prior; + } else if (KeyAnalyzer.isNullBitKey(bitIndex)) { + return null; + } else if (KeyAnalyzer.isEqualBitKey(bitIndex)) { + return previousEntry(found); + } + + // we should have exited above. + throw new IllegalStateException("invalid lookup: " + key); + } + + /** + * Returns a key-value mapping associated with the greatest key + * less than or equal to the given key, or null if there is no such key. + */ + TrieEntry floorEntry(final K key) { + // TODO: Cleanup so that we don't actually have to add/remove from the + // tree. (We do it here because there are other well-defined + // functions to perform the search.) + final int lengthInBits = lengthInBits(key); + + if (lengthInBits == 0) { + if (!root.isEmpty()) { + return root; + } else { + return null; + } + } + + final TrieEntry found = getNearestEntryForKey(key, lengthInBits); + if (compareKeys(key, found.key)) { + return found; + } + + final int bitIndex = bitIndex(key, found.key); + if (KeyAnalyzer.isValidBitIndex(bitIndex)) { + final TrieEntry added = new TrieEntry(key, null, bitIndex); + addEntry(added, lengthInBits); + incrementSize(); // must increment because remove will decrement + final TrieEntry floor = previousEntry(added); + removeEntry(added); + modCount -= 2; // we didn't really modify it. + return floor; + } else if (KeyAnalyzer.isNullBitKey(bitIndex)) { + if (!root.isEmpty()) { + return root; + } else { + return null; + } + } else if (KeyAnalyzer.isEqualBitKey(bitIndex)) { + return found; + } + + // we should have exited above. + throw new IllegalStateException("invalid lookup: " + key); + } + + /** + * Finds the subtree that contains the prefix. + * + * This is very similar to getR but with the difference that + * we stop the lookup if h.bitIndex > lengthInBits. + */ + TrieEntry subtree(final K prefix, final int offsetInBits, final int lengthInBits) { + TrieEntry current = root.left; + TrieEntry path = root; + while(true) { + if (current.bitIndex <= path.bitIndex || lengthInBits < current.bitIndex) { + break; + } + + path = current; + if (!isBitSet(prefix, offsetInBits + current.bitIndex, offsetInBits + lengthInBits)) { + current = current.left; + } else { + current = current.right; + } + } + + // Make sure the entry is valid for a subtree. + final TrieEntry entry = current.isEmpty() ? path : current; + + // If entry is root, it can't be empty. + if (entry.isEmpty()) { + return null; + } + + final int endIndexInBits = offsetInBits + lengthInBits; + + // if root && length of root is less than length of lookup, + // there's nothing. + // (this prevents returning the whole subtree if root has an empty + // string and we want to lookup things with "\0") + if (entry == root && lengthInBits(entry.getKey()) < endIndexInBits) { + return null; + } + + // Found key's length-th bit differs from our key + // which means it cannot be the prefix... + if (isBitSet(prefix, endIndexInBits, endIndexInBits) + != isBitSet(entry.key, lengthInBits, lengthInBits(entry.key))) { + return null; + } + + // ... or there are less than 'length' equal bits + final int bitIndex = getKeyAnalyzer().bitIndex(prefix, offsetInBits, lengthInBits, + entry.key, 0, lengthInBits(entry.getKey())); + + if (bitIndex >= 0 && bitIndex < lengthInBits) { + return null; + } + + return entry; + } + + /** + * Returns the last entry the {@link Trie} is storing. + * + *

This is implemented by going always to the right until + * we encounter a valid uplink. That uplink is the last key. + */ + TrieEntry lastEntry() { + return followRight(root.left); + } + + /** + * Traverses down the right path until it finds an uplink. + */ + TrieEntry followRight(TrieEntry node) { + // if Trie is empty, no last entry. + if (node.right == null) { + return null; + } + + // Go as far right as possible, until we encounter an uplink. + while (node.right.bitIndex > node.bitIndex) { + node = node.right; + } + + return node.right; + } + + /** + * Returns the node lexicographically before the given node (or null if none). + * + * This follows four simple branches: + * - If the uplink that returned us was a right uplink: + * - If predecessor's left is a valid uplink from predecessor, return it. + * - Else, follow the right path from the predecessor's left. + * - If the uplink that returned us was a left uplink: + * - Loop back through parents until we encounter a node where + * node != node.parent.left. + * - If node.parent.left is uplink from node.parent: + * - If node.parent.left is not root, return it. + * - If it is root & root isEmpty, return null. + * - If it is root & root !isEmpty, return root. + * - If node.parent.left is not uplink from node.parent: + * - Follow right path for first right child from node.parent.left + * + * @param start the start entry + */ + TrieEntry previousEntry(final TrieEntry start) { + if (start.predecessor == null) { + throw new IllegalArgumentException("must have come from somewhere!"); + } + + if (start.predecessor.right == start) { + if (isValidUplink(start.predecessor.left, start.predecessor)) { + return start.predecessor.left; + } else { + return followRight(start.predecessor.left); + } + } else { + TrieEntry node = start.predecessor; + while (node.parent != null && node == node.parent.left) { + node = node.parent; + } + + if (node.parent == null) { // can be null if we're looking up root. + return null; + } + + if (isValidUplink(node.parent.left, node.parent)) { + if (node.parent.left == root) { + if (root.isEmpty()) { + return null; + } else { + return root; + } + + } else { + return node.parent.left; + } + } else { + return followRight(node.parent.left); + } + } + } + + /** + * Returns the entry lexicographically after the given entry. + * If the given entry is null, returns the first node. + * + * This will traverse only within the subtree. If the given node + * is not within the subtree, this will have undefined results. + */ + TrieEntry nextEntryInSubtree(final TrieEntry node, + final TrieEntry parentOfSubtree) { + if (node == null) { + return firstEntry(); + } else { + return nextEntryImpl(node.predecessor, node, parentOfSubtree); + } + } + + /** + * Returns true if 'next' is a valid uplink coming from 'from'. + */ + static boolean isValidUplink(final TrieEntry next, final TrieEntry from) { + return next != null && next.bitIndex <= from.bitIndex && !next.isEmpty(); + } + + /** + * A {@link Reference} allows us to return something through a Method's + * argument list. An alternative would be to an Array with a length of + * one (1) but that leads to compiler warnings. Computationally and memory + * wise there's no difference (except for the need to load the + * {@link Reference} Class but that happens only once). + */ + private static class Reference { + + private E item; + + public void set(final E item) { + this.item = item; + } + + public E get() { + return item; + } + } + + /** + * A {@link Trie} is a set of {@link TrieEntry} nodes. + */ + protected static class TrieEntry extends BasicEntry { + + private static final long serialVersionUID = 4596023148184140013L; + + /** The index this entry is comparing. */ + protected int bitIndex; + + /** The parent of this entry. */ + protected TrieEntry parent; + + /** The left child of this entry. */ + protected TrieEntry left; + + /** The right child of this entry. */ + protected TrieEntry right; + + /** The entry who uplinks to this entry. */ + protected TrieEntry predecessor; + + public TrieEntry(final K key, final V value, final int bitIndex) { + super(key, value); + + this.bitIndex = bitIndex; + + this.parent = null; + this.left = this; + this.right = null; + this.predecessor = this; + } + + /** + * Whether or not the entry is storing a key. + * Only the root can potentially be empty, all other + * nodes must have a key. + */ + public boolean isEmpty() { + return key == null; + } + + /** + * Neither the left nor right child is a loopback. + */ + public boolean isInternalNode() { + return left != this && right != this; + } + + /** + * Either the left or right child is a loopback. + */ + public boolean isExternalNode() { + return !isInternalNode(); + } + + @Override + public String toString() { + final StringBuilder buffer = new StringBuilder(); + + if (bitIndex == -1) { + buffer.append("RootEntry("); + } else { + buffer.append("Entry("); + } + + buffer.append("key=").append(getKey()).append(" [").append(bitIndex).append("], "); + buffer.append("value=").append(getValue()).append(", "); + //buffer.append("bitIndex=").append(bitIndex).append(", "); + + if (parent != null) { + if (parent.bitIndex == -1) { + buffer.append("parent=").append("ROOT"); + } else { + buffer.append("parent=").append(parent.getKey()).append(" [").append(parent.bitIndex).append("]"); + } + } else { + buffer.append("parent=").append("null"); + } + buffer.append(", "); + + if (left != null) { + if (left.bitIndex == -1) { + buffer.append("left=").append("ROOT"); + } else { + buffer.append("left=").append(left.getKey()).append(" [").append(left.bitIndex).append("]"); + } + } else { + buffer.append("left=").append("null"); + } + buffer.append(", "); + + if (right != null) { + if (right.bitIndex == -1) { + buffer.append("right=").append("ROOT"); + } else { + buffer.append("right=").append(right.getKey()).append(" [").append(right.bitIndex).append("]"); + } + } else { + buffer.append("right=").append("null"); + } + buffer.append(", "); + + if (predecessor != null) { + if(predecessor.bitIndex == -1) { + buffer.append("predecessor=").append("ROOT"); + } else { + buffer.append("predecessor=").append(predecessor.getKey()).append(" ["). + append(predecessor.bitIndex).append("]"); + } + } + + buffer.append(")"); + return buffer.toString(); + } + } + + + /** + * This is a entry set view of the {@link Trie} as returned by {@link Map#entrySet()}. + */ + private class EntrySet extends AbstractSet> { + + @Override + public Iterator> iterator() { + return new EntryIterator(); + } + + @Override + public boolean contains(final Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + + final TrieEntry candidate = getEntry(((Map.Entry)o).getKey()); + return candidate != null && candidate.equals(o); + } + + @Override + public boolean remove(final Object obj) { + if (obj instanceof Map.Entry == false) { + return false; + } + if (contains(obj) == false) { + return false; + } + final Map.Entry entry = (Map.Entry) obj; + AbstractPatriciaTrie.this.remove(entry.getKey()); + return true; + } + + @Override + public int size() { + return AbstractPatriciaTrie.this.size(); + } + + @Override + public void clear() { + AbstractPatriciaTrie.this.clear(); + } + + /** + * An {@link Iterator} that returns {@link Entry} Objects. + */ + private class EntryIterator extends TrieIterator> { + public Map.Entry next() { + return nextEntry(); + } + } + } + + /** + * This is a key set view of the {@link Trie} as returned by {@link Map#keySet()}. + */ + private class KeySet extends AbstractSet { + + @Override + public Iterator iterator() { + return new KeyIterator(); + } + + @Override + public int size() { + return AbstractPatriciaTrie.this.size(); + } + + @Override + public boolean contains(final Object o) { + return containsKey(o); + } + + @Override + public boolean remove(final Object o) { + final int size = size(); + AbstractPatriciaTrie.this.remove(o); + return size != size(); + } + + @Override + public void clear() { + AbstractPatriciaTrie.this.clear(); + } + + /** + * An {@link Iterator} that returns Key Objects. + */ + private class KeyIterator extends TrieIterator { + public K next() { + return nextEntry().getKey(); + } + } + } + + /** + * This is a value view of the {@link Trie} as returned by {@link Map#values()}. + */ + private class Values extends AbstractCollection { + + @Override + public Iterator iterator() { + return new ValueIterator(); + } + + @Override + public int size() { + return AbstractPatriciaTrie.this.size(); + } + + @Override + public boolean contains(final Object o) { + return containsValue(o); + } + + @Override + public void clear() { + AbstractPatriciaTrie.this.clear(); + } + + @Override + public boolean remove(final Object o) { + for (final Iterator it = iterator(); it.hasNext(); ) { + final V value = it.next(); + if (compare(value, o)) { + it.remove(); + return true; + } + } + return false; + } + + /** + * An {@link Iterator} that returns Value Objects. + */ + private class ValueIterator extends TrieIterator { + public V next() { + return nextEntry().getValue(); + } + } + } + + /** + * An iterator for the entries. + */ + abstract class TrieIterator implements Iterator { + + /** For fast-fail. */ + protected int expectedModCount = AbstractPatriciaTrie.this.modCount; + + protected TrieEntry next; // the next node to return + protected TrieEntry current; // the current entry we're on + + /** + * Starts iteration from the root. + */ + protected TrieIterator() { + next = AbstractPatriciaTrie.this.nextEntry(null); + } + + /** + * Starts iteration at the given entry. + */ + protected TrieIterator(final TrieEntry firstEntry) { + next = firstEntry; + } + + /** + * Returns the next {@link TrieEntry}. + */ + protected TrieEntry nextEntry() { + if (expectedModCount != AbstractPatriciaTrie.this.modCount) { + throw new ConcurrentModificationException(); + } + + final TrieEntry e = next; + if (e == null) { + throw new NoSuchElementException(); + } + + next = findNext(e); + current = e; + return e; + } + + /** + * @see PatriciaTrie#nextEntry(TrieEntry) + */ + protected TrieEntry findNext(final TrieEntry prior) { + return AbstractPatriciaTrie.this.nextEntry(prior); + } + + public boolean hasNext() { + return next != null; + } + + public void remove() { + if (current == null) { + throw new IllegalStateException(); + } + + if (expectedModCount != AbstractPatriciaTrie.this.modCount) { + throw new ConcurrentModificationException(); + } + + final TrieEntry node = current; + current = null; + AbstractPatriciaTrie.this.removeEntry(node); + + expectedModCount = AbstractPatriciaTrie.this.modCount; + } + } + + /** + * An {@link OrderedMapIterator} for a {@link Trie}. + */ + private class TrieMapIterator extends TrieIterator implements OrderedMapIterator { + + protected TrieEntry previous; // the previous node to return + + public K next() { + return nextEntry().getKey(); + } + + public K getKey() { + if (current == null) { + throw new IllegalStateException(); + } + return current.getKey(); + } + + public V getValue() { + if (current == null) { + throw new IllegalStateException(); + } + return current.getValue(); + } + + public V setValue(final V value) { + if (current == null) { + throw new IllegalStateException(); + } + return current.setValue(value); + } + + public boolean hasPrevious() { + return previous != null; + } + + public K previous() { + return previousEntry().getKey(); + } + + @Override + protected TrieEntry nextEntry() { + final TrieEntry nextEntry = super.nextEntry(); + previous = nextEntry; + return nextEntry; + } + + protected TrieEntry previousEntry() { + if (expectedModCount != AbstractPatriciaTrie.this.modCount) { + throw new ConcurrentModificationException(); + } + + final TrieEntry e = previous; + if (e == null) { + throw new NoSuchElementException(); + } + + previous = AbstractPatriciaTrie.this.previousEntry(e); + next = current; + current = e; + return current; + } + + } + + /** + * A range view of the {@link Trie}. + */ + private abstract class RangeMap extends AbstractMap + implements SortedMap { + + /** The {@link #entrySet()} view. */ + private transient volatile Set> entrySet; + + /** + * Creates and returns an {@link #entrySet()} view of the {@link RangeMap}. + */ + protected abstract Set> createEntrySet(); + + /** + * Returns the FROM Key. + */ + protected abstract K getFromKey(); + + /** + * Whether or not the {@link #getFromKey()} is in the range. + */ + protected abstract boolean isFromInclusive(); + + /** + * Returns the TO Key. + */ + protected abstract K getToKey(); + + /** + * Whether or not the {@link #getToKey()} is in the range. + */ + protected abstract boolean isToInclusive(); + + public Comparator comparator() { + return AbstractPatriciaTrie.this.comparator(); + } + + @Override + public boolean containsKey(final Object key) { + if (!inRange(castKey(key))) { + return false; + } + + return AbstractPatriciaTrie.this.containsKey(key); + } + + @Override + public V remove(final Object key) { + if (!inRange(castKey(key))) { + return null; + } + + return AbstractPatriciaTrie.this.remove(key); + } + + @Override + public V get(final Object key) { + if (!inRange(castKey(key))) { + return null; + } + + return AbstractPatriciaTrie.this.get(key); + } + + @Override + public V put(final K key, final V value) { + if (!inRange(key)) { + throw new IllegalArgumentException("Key is out of range: " + key); + } + return AbstractPatriciaTrie.this.put(key, value); + } + + @Override + public Set> entrySet() { + if (entrySet == null) { + entrySet = createEntrySet(); + } + return entrySet; + } + + public SortedMap subMap(final K fromKey, final K toKey) { + if (!inRange2(fromKey)) { + throw new IllegalArgumentException("FromKey is out of range: " + fromKey); + } + + if (!inRange2(toKey)) { + throw new IllegalArgumentException("ToKey is out of range: " + toKey); + } + + return createRangeMap(fromKey, isFromInclusive(), toKey, isToInclusive()); + } + + public SortedMap headMap(final K toKey) { + if (!inRange2(toKey)) { + throw new IllegalArgumentException("ToKey is out of range: " + toKey); + } + return createRangeMap(getFromKey(), isFromInclusive(), toKey, isToInclusive()); + } + + public SortedMap tailMap(final K fromKey) { + if (!inRange2(fromKey)) { + throw new IllegalArgumentException("FromKey is out of range: " + fromKey); + } + return createRangeMap(fromKey, isFromInclusive(), getToKey(), isToInclusive()); + } + + /** + * Returns true if the provided key is greater than TO and less than FROM. + */ + protected boolean inRange(final K key) { + final K fromKey = getFromKey(); + final K toKey = getToKey(); + + return (fromKey == null || inFromRange(key, false)) && (toKey == null || inToRange(key, false)); + } + + /** + * This form allows the high endpoint (as well as all legit keys). + */ + protected boolean inRange2(final K key) { + final K fromKey = getFromKey(); + final K toKey = getToKey(); + + return (fromKey == null || inFromRange(key, false)) && (toKey == null || inToRange(key, true)); + } + + /** + * Returns true if the provided key is in the FROM range of the {@link RangeMap}. + */ + protected boolean inFromRange(final K key, final boolean forceInclusive) { + final K fromKey = getFromKey(); + final boolean fromInclusive = isFromInclusive(); + + final int ret = getKeyAnalyzer().compare(key, fromKey); + if (fromInclusive || forceInclusive) { + return ret >= 0; + } else { + return ret > 0; + } + } + + /** + * Returns true if the provided key is in the TO range of the {@link RangeMap}. + */ + protected boolean inToRange(final K key, final boolean forceInclusive) { + final K toKey = getToKey(); + final boolean toInclusive = isToInclusive(); + + final int ret = getKeyAnalyzer().compare(key, toKey); + if (toInclusive || forceInclusive) { + return ret <= 0; + } else { + return ret < 0; + } + } + + /** + * Creates and returns a sub-range view of the current {@link RangeMap}. + */ + protected abstract SortedMap createRangeMap(K fromKey, boolean fromInclusive, + K toKey, boolean toInclusive); + } + + /** + * A {@link RangeMap} that deals with {@link Entry}s. + */ + private class RangeEntryMap extends RangeMap { + + /** The key to start from, null if the beginning. */ + private final K fromKey; + + /** The key to end at, null if till the end. */ + private final K toKey; + + /** Whether or not the 'from' is inclusive. */ + private final boolean fromInclusive; + + /** Whether or not the 'to' is inclusive. */ + private final boolean toInclusive; + + /** + * Creates a {@link RangeEntryMap} with the fromKey included and + * the toKey excluded from the range. + */ + protected RangeEntryMap(final K fromKey, final K toKey) { + this(fromKey, true, toKey, false); + } + + /** + * Creates a {@link RangeEntryMap}. + */ + protected RangeEntryMap(final K fromKey, final boolean fromInclusive, + final K toKey, final boolean toInclusive) { + + if (fromKey == null && toKey == null) { + throw new IllegalArgumentException("must have a from or to!"); + } + + if (fromKey != null && toKey != null && getKeyAnalyzer().compare(fromKey, toKey) > 0) { + throw new IllegalArgumentException("fromKey > toKey"); + } + + this.fromKey = fromKey; + this.fromInclusive = fromInclusive; + this.toKey = toKey; + this.toInclusive = toInclusive; + } + + public K firstKey() { + Map.Entry e = null; + if (fromKey == null) { + e = firstEntry(); + } else { + if (fromInclusive) { + e = ceilingEntry(fromKey); + } else { + e = higherEntry(fromKey); + } + } + + final K first = e != null ? e.getKey() : null; + if (e == null || toKey != null && !inToRange(first, false)) { + throw new NoSuchElementException(); + } + return first; + } + + public K lastKey() { + Map.Entry e; + if (toKey == null) { + e = lastEntry(); + } else { + if (toInclusive) { + e = floorEntry(toKey); + } else { + e = lowerEntry(toKey); + } + } + + final K last = e != null ? e.getKey() : null; + if (e == null || fromKey != null && !inFromRange(last, false)) { + throw new NoSuchElementException(); + } + return last; + } + + @Override + protected Set> createEntrySet() { + return new RangeEntrySet(this); + } + + @Override + public K getFromKey() { + return fromKey; + } + + @Override + public K getToKey() { + return toKey; + } + + @Override + public boolean isFromInclusive() { + return fromInclusive; + } + + @Override + public boolean isToInclusive() { + return toInclusive; + } + + @Override + protected SortedMap createRangeMap(final K fromKey, final boolean fromInclusive, + final K toKey, final boolean toInclusive) { + return new RangeEntryMap(fromKey, fromInclusive, toKey, toInclusive); + } + } + + /** + * A {@link Set} view of a {@link RangeMap}. + */ + private class RangeEntrySet extends AbstractSet> { + + private final RangeMap delegate; + + private transient int size = -1; + + private transient int expectedModCount; + + /** + * Creates a {@link RangeEntrySet}. + */ + public RangeEntrySet(final RangeMap delegate) { + if (delegate == null) { + throw new NullPointerException("delegate"); + } + + this.delegate = delegate; + } + + @Override + public Iterator> iterator() { + final K fromKey = delegate.getFromKey(); + final K toKey = delegate.getToKey(); + + TrieEntry first = null; + if (fromKey == null) { + first = firstEntry(); + } else { + first = ceilingEntry(fromKey); + } + + TrieEntry last = null; + if (toKey != null) { + last = ceilingEntry(toKey); + } + + return new EntryIterator(first, last); + } + + @Override + public int size() { + if (size == -1 || expectedModCount != AbstractPatriciaTrie.this.modCount) { + size = 0; + + for (final Iterator it = iterator(); it.hasNext(); it.next()) { + ++size; + } + + expectedModCount = AbstractPatriciaTrie.this.modCount; + } + return size; + } + + @Override + public boolean isEmpty() { + return !iterator().hasNext(); + } + + @SuppressWarnings("unchecked") + @Override + public boolean contains(final Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + + final Map.Entry entry = (Map.Entry) o; + final K key = entry.getKey(); + if (!delegate.inRange(key)) { + return false; + } + + final TrieEntry node = getEntry(key); + return node != null && compare(node.getValue(), entry.getValue()); + } + + @SuppressWarnings("unchecked") + @Override + public boolean remove(final Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + + final Map.Entry entry = (Map.Entry) o; + final K key = entry.getKey(); + if (!delegate.inRange(key)) { + return false; + } + + final TrieEntry node = getEntry(key); + if (node != null && compare(node.getValue(), entry.getValue())) { + removeEntry(node); + return true; + } + return false; + } + + /** + * An {@link Iterator} for {@link RangeEntrySet}s. + */ + private final class EntryIterator extends TrieIterator> { + + private final K excludedKey; + + /** + * Creates a {@link EntryIterator}. + */ + private EntryIterator(final TrieEntry first, final TrieEntry last) { + super(first); + this.excludedKey = last != null ? last.getKey() : null; + } + + @Override + public boolean hasNext() { + return next != null && !compare(next.key, excludedKey); + } + + public Map.Entry next() { + if (next == null || compare(next.key, excludedKey)) { + throw new NoSuchElementException(); + } + return nextEntry(); + } + } + } + + /** + * A submap used for prefix views over the {@link Trie}. + */ + private class PrefixRangeMap extends RangeMap { + + private final K prefix; + + private final int offsetInBits; + + private final int lengthInBits; + + private K fromKey = null; + + private K toKey = null; + + private transient int expectedModCount = 0; + + private int size = -1; + + /** + * Creates a {@link PrefixRangeMap}. + */ + private PrefixRangeMap(final K prefix, final int offsetInBits, final int lengthInBits) { + this.prefix = prefix; + this.offsetInBits = offsetInBits; + this.lengthInBits = lengthInBits; + } + + /** + * This method does two things. It determines the FROM + * and TO range of the {@link PrefixRangeMap} and the number + * of elements in the range. This method must be called every + * time the {@link Trie} has changed. + */ + private int fixup() { + // The trie has changed since we last found our toKey / fromKey + if (size == - 1 || AbstractPatriciaTrie.this.modCount != expectedModCount) { + final Iterator> it = entrySet().iterator(); + size = 0; + + Map.Entry entry = null; + if (it.hasNext()) { + entry = it.next(); + size = 1; + } + + fromKey = entry == null ? null : entry.getKey(); + if (fromKey != null) { + final TrieEntry prior = previousEntry((TrieEntry)entry); + fromKey = prior == null ? null : prior.getKey(); + } + + toKey = fromKey; + + while (it.hasNext()) { + ++size; + entry = it.next(); + } + + toKey = entry == null ? null : entry.getKey(); + + if (toKey != null) { + entry = nextEntry((TrieEntry)entry); + toKey = entry == null ? null : entry.getKey(); + } + + expectedModCount = AbstractPatriciaTrie.this.modCount; + } + + return size; + } + + public K firstKey() { + fixup(); + + Map.Entry e = null; + if (fromKey == null) { + e = firstEntry(); + } else { + e = higherEntry(fromKey); + } + + final K first = e != null ? e.getKey() : null; + if (e == null || !getKeyAnalyzer().isPrefix(prefix, offsetInBits, lengthInBits, first)) { + throw new NoSuchElementException(); + } + + return first; + } + + public K lastKey() { + fixup(); + + Map.Entry e = null; + if (toKey == null) { + e = lastEntry(); + } else { + e = lowerEntry(toKey); + } + + final K last = e != null ? e.getKey() : null; + if (e == null || !getKeyAnalyzer().isPrefix(prefix, offsetInBits, lengthInBits, last)) { + throw new NoSuchElementException(); + } + + return last; + } + + /** + * Returns true if this {@link PrefixRangeMap}'s key is a prefix of the provided key. + */ + @Override + protected boolean inRange(final K key) { + return getKeyAnalyzer().isPrefix(prefix, offsetInBits, lengthInBits, key); + } + + /** + * Same as {@link #inRange(Object)}. + */ + @Override + protected boolean inRange2(final K key) { + return inRange(key); + } + + /** + * Returns true if the provided Key is in the FROM range of the {@link PrefixRangeMap}. + */ + @Override + protected boolean inFromRange(final K key, final boolean forceInclusive) { + return getKeyAnalyzer().isPrefix(prefix, offsetInBits, lengthInBits, key); + } + + /** + * Returns true if the provided Key is in the TO range of the {@link PrefixRangeMap}. + */ + @Override + protected boolean inToRange(final K key, final boolean forceInclusive) { + return getKeyAnalyzer().isPrefix(prefix, offsetInBits, lengthInBits, key); + } + + @Override + protected Set> createEntrySet() { + return new PrefixRangeEntrySet(this); + } + + @Override + public K getFromKey() { + return fromKey; + } + + @Override + public K getToKey() { + return toKey; + } + + @Override + public boolean isFromInclusive() { + return false; + } + + @Override + public boolean isToInclusive() { + return false; + } + + @Override + protected SortedMap createRangeMap(final K fromKey, final boolean fromInclusive, + final K toKey, final boolean toInclusive) { + return new RangeEntryMap(fromKey, fromInclusive, toKey, toInclusive); + } + } + + /** + * A prefix {@link RangeEntrySet} view of the {@link Trie}. + */ + private final class PrefixRangeEntrySet extends RangeEntrySet { + + private final PrefixRangeMap delegate; + + private TrieEntry prefixStart; + + private int expectedModCount = 0; + + /** + * Creates a {@link PrefixRangeEntrySet}. + */ + public PrefixRangeEntrySet(final PrefixRangeMap delegate) { + super(delegate); + this.delegate = delegate; + } + + @Override + public int size() { + return delegate.fixup(); + } + + @Override + public Iterator> iterator() { + if (AbstractPatriciaTrie.this.modCount != expectedModCount) { + prefixStart = subtree(delegate.prefix, delegate.offsetInBits, delegate.lengthInBits); + expectedModCount = AbstractPatriciaTrie.this.modCount; + } + + if (prefixStart == null) { + final Set> empty = Collections.emptySet(); + return empty.iterator(); + } else if (delegate.lengthInBits >= prefixStart.bitIndex) { + return new SingletonIterator(prefixStart); + } else { + return new EntryIterator(prefixStart, delegate.prefix, delegate.offsetInBits, delegate.lengthInBits); + } + } + + /** + * An {@link Iterator} that holds a single {@link TrieEntry}. + */ + private final class SingletonIterator implements Iterator> { + + private final TrieEntry entry; + + private int hit = 0; + + public SingletonIterator(final TrieEntry entry) { + this.entry = entry; + } + + public boolean hasNext() { + return hit == 0; + } + + public Map.Entry next() { + if (hit != 0) { + throw new NoSuchElementException(); + } + + ++hit; + return entry; + } + + public void remove() { + if (hit != 1) { + throw new IllegalStateException(); + } + + ++hit; + AbstractPatriciaTrie.this.removeEntry(entry); + } + } + + /** + * An {@link Iterator} for iterating over a prefix search. + */ + private final class EntryIterator extends TrieIterator> { + + // values to reset the subtree if we remove it. + private final K prefix; + private final int offset; + private final int lengthInBits; + private boolean lastOne; + + private TrieEntry subtree; // the subtree to search within + + /** + * Starts iteration at the given entry & search only + * within the given subtree. + */ + EntryIterator(final TrieEntry startScan, final K prefix, + final int offset, final int lengthInBits) { + subtree = startScan; + next = AbstractPatriciaTrie.this.followLeft(startScan); + this.prefix = prefix; + this.offset = offset; + this.lengthInBits = lengthInBits; + } + + public Map.Entry next() { + final Map.Entry entry = nextEntry(); + if (lastOne) { + next = null; + } + return entry; + } + + @Override + protected TrieEntry findNext(final TrieEntry prior) { + return AbstractPatriciaTrie.this.nextEntryInSubtree(prior, subtree); + } + + @Override + public void remove() { + // If the current entry we're removing is the subtree + // then we need to find a new subtree parent. + boolean needsFixing = false; + final int bitIdx = subtree.bitIndex; + if (current == subtree) { + needsFixing = true; + } + + super.remove(); + + // If the subtree changed its bitIndex or we + // removed the old subtree, get a new one. + if (bitIdx != subtree.bitIndex || needsFixing) { + subtree = subtree(prefix, offset, lengthInBits); + } + + // If the subtree's bitIndex is less than the + // length of our prefix, it's the last item + // in the prefix tree. + if (lengthInBits >= subtree.bitIndex) { + lastOne = true; + } + } + } + } + + //----------------------------------------------------------------------- + + /** + * Reads the content of the stream. + */ + @SuppressWarnings("unchecked") // This will fail at runtime if the stream is incorrect + private void readObject(final ObjectInputStream stream) throws IOException, ClassNotFoundException{ + stream.defaultReadObject(); + root = new TrieEntry(null, null, -1); + int size = stream.readInt(); + for(int i = 0; i < size; i++){ + K k = (K) stream.readObject(); + V v = (V) stream.readObject(); + put(k, v); + } + } + + /** + * Writes the content to the stream for serialization. + */ + private void writeObject(final ObjectOutputStream stream) throws IOException{ + stream.defaultWriteObject(); + stream.writeInt(this.size()); + for (final Entry entry : entrySet()) { + stream.writeObject(entry.getKey()); + stream.writeObject(entry.getValue()); + } + } + +} diff --git a/src/main/java/org/apache/commons/collections4/trie/PatriciaTrie.java b/src/main/java/org/apache/commons/collections4/trie/PatriciaTrie.java index 1f3d2b751..350426503 100644 --- a/src/main/java/org/apache/commons/collections4/trie/PatriciaTrie.java +++ b/src/main/java/org/apache/commons/collections4/trie/PatriciaTrie.java @@ -16,17 +16,10 @@ */ package org.apache.commons.collections4.trie; -import java.util.AbstractMap; -import java.util.AbstractSet; -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.SortedMap; import org.apache.commons.collections4.Trie; +import org.apache.commons.collections4.trie.analyzer.StringKeyAnalyzer; /** * Implementation of a PATRICIA Trie (Practical Algorithm to Retrieve Information @@ -47,20 +40,14 @@ import org.apache.commons.collections4.Trie; * the given key, instead of comparing the entire key to another key. *

* The {@link Trie} can return operations in lexicographical order using the - * {@link #traverse(Cursor)}, 'prefix', 'submap', or 'iterator' methods. The - * {@link Trie} can also scan for items that are 'bitwise' (using an XOR - * metric) by the 'select' method. Bitwise closeness is determined by the - * {@link KeyAnalyzer} returning true or false for a bit being set or not in - * a given key. + * 'prefixMap', 'submap', or 'iterator' methods. The {@link Trie} can also + * scan for items that are 'bitwise' (using an XOR metric) by the 'select' method. + * Bitwise closeness is determined by the {@link KeyAnalyzer} returning true or + * false for a bit being set or not in a given key. *

* This PATRICIA {@link Trie} supports both variable length & fixed length - * keys. Some methods, such as {@link #getPrefixedBy(Object)} are suited only - * to variable length keys, whereas {@link #getPrefixedByBits(Object, int)} is - * suited to fixed-size keys. - *

- * Any methods here that take an {@link Object} argument may throw a - * {@link ClassCastException} if the method is expecting an instance of K - * and it isn't K. + * keys. Some methods, such as {@link #prefixMap(Object)} are suited only + * to variable length keys. * * @see Radix Tree * @see PATRICIA @@ -68,1187 +55,16 @@ import org.apache.commons.collections4.Trie; * @since 4.0 * @version $Id$ */ -public class PatriciaTrie extends PatriciaTrieBase implements Trie { +public class PatriciaTrie extends AbstractPatriciaTrie implements Trie { private static final long serialVersionUID = 4446367780901817838L; - public PatriciaTrie(final KeyAnalyzer keyAnalyzer) { - super(keyAnalyzer); + public PatriciaTrie() { + super(new StringKeyAnalyzer()); } - public PatriciaTrie(final KeyAnalyzer keyAnalyzer, final Map m) { - super(keyAnalyzer, m); + public PatriciaTrie(final Map m) { + super(new StringKeyAnalyzer(), m); } - public Comparator comparator() { - return keyAnalyzer; - } - - public SortedMap getPrefixedBy(final K key) { - return getPrefixedByBits(key, 0, lengthInBits(key)); - } - - public SortedMap getPrefixedBy(final K key, final int length) { - return getPrefixedByBits(key, 0, length * bitsPerElement()); - } - - public SortedMap getPrefixedBy(final K key, final int offset, final int length) { - final int bitsPerElement = bitsPerElement(); - return getPrefixedByBits(key, offset*bitsPerElement, length*bitsPerElement); - } - - public SortedMap getPrefixedByBits(final K key, final int lengthInBits) { - return getPrefixedByBits(key, 0, lengthInBits); - } - - public K firstKey() { - return firstEntry().getKey(); - } - - public K lastKey() { - final TrieEntry entry = lastEntry(); - if (entry != null) { - return entry.getKey(); - } - return null; - } - - /** - * {@inheritDoc} - * - * The view that this returns is optimized to have a very efficient - * {@link Iterator}. The {@link SortedMap#firstKey()}, - * {@link SortedMap#lastKey()} & {@link Map#size()} methods must - * iterate over all possible values in order to determine the results. - * This information is cached until the PATRICIA {@link Trie} changes. - * All other methods (except {@link Iterator}) must compare the given - * key to the prefix to ensure that it is within the range of the view. - * The {@link Iterator}'s remove method must also relocate the subtree - * that contains the prefixes if the entry holding the subtree is - * removed or changes. Changing the subtree takes O(K) time. - */ - public SortedMap getPrefixedByBits(final K key, final int offsetInBits, final int lengthInBits) { - - final int offsetLength = offsetInBits + lengthInBits; - if (offsetLength > lengthInBits(key)) { - throw new IllegalArgumentException(offsetInBits + " + " - + lengthInBits + " > " + lengthInBits(key)); - } - - if (offsetLength == 0) { - return this; - } - - return new PrefixRangeMap(key, offsetInBits, lengthInBits); - } - - public SortedMap headMap(final K toKey) { - return new RangeEntryMap(null, toKey); - } - - public SortedMap subMap(final K fromKey, final K toKey) { - return new RangeEntryMap(fromKey, toKey); - } - - public SortedMap tailMap(final K fromKey) { - return new RangeEntryMap(fromKey, null); - } - - /** - * Returns an entry strictly higher than the given key, - * or null if no such entry exists. - */ - TrieEntry higherEntry(final K key) { - // TODO: Cleanup so that we don't actually have to add/remove from the - // tree. (We do it here because there are other well-defined - // functions to perform the search.) - final int lengthInBits = lengthInBits(key); - - if (lengthInBits == 0) { - if (!root.isEmpty()) { - // If data in root, and more after -- return it. - if (size() > 1) { - return nextEntry(root); - } else { // If no more after, no higher entry. - return null; - } - } else { - // Root is empty & we want something after empty, return first. - return firstEntry(); - } - } - - final TrieEntry found = getNearestEntryForKey(key, lengthInBits); - if (compareKeys(key, found.key)) { - return nextEntry(found); - } - - final int bitIndex = bitIndex(key, found.key); - if (KeyAnalyzer.isValidBitIndex(bitIndex)) { - final TrieEntry added = new TrieEntry(key, null, bitIndex); - addEntry(added, lengthInBits); - incrementSize(); // must increment because remove will decrement - final TrieEntry ceil = nextEntry(added); - removeEntry(added); - modCount -= 2; // we didn't really modify it. - return ceil; - } else if (KeyAnalyzer.isNullBitKey(bitIndex)) { - if (!root.isEmpty()) { - return firstEntry(); - } else if (size() > 1) { - return nextEntry(firstEntry()); - } else { - return null; - } - } else if (KeyAnalyzer.isEqualBitKey(bitIndex)) { - return nextEntry(found); - } - - // we should have exited above. - throw new IllegalStateException("invalid lookup: " + key); - } - - /** - * Returns a key-value mapping associated with the least key greater - * than or equal to the given key, or null if there is no such key. - */ - TrieEntry ceilingEntry(final K key) { - // Basically: - // Follow the steps of adding an entry, but instead... - // - // - If we ever encounter a situation where we found an equal - // key, we return it immediately. - // - // - If we hit an empty root, return the first iterable item. - // - // - If we have to add a new item, we temporarily add it, - // find the successor to it, then remove the added item. - // - // These steps ensure that the returned value is either the - // entry for the key itself, or the first entry directly after - // the key. - - // TODO: Cleanup so that we don't actually have to add/remove from the - // tree. (We do it here because there are other well-defined - // functions to perform the search.) - final int lengthInBits = lengthInBits(key); - - if (lengthInBits == 0) { - if (!root.isEmpty()) { - return root; - } else { - return firstEntry(); - } - } - - final TrieEntry found = getNearestEntryForKey(key, lengthInBits); - if (compareKeys(key, found.key)) { - return found; - } - - final int bitIndex = bitIndex(key, found.key); - if (KeyAnalyzer.isValidBitIndex(bitIndex)) { - final TrieEntry added = new TrieEntry(key, null, bitIndex); - addEntry(added, lengthInBits); - incrementSize(); // must increment because remove will decrement - final TrieEntry ceil = nextEntry(added); - removeEntry(added); - modCount -= 2; // we didn't really modify it. - return ceil; - } else if (KeyAnalyzer.isNullBitKey(bitIndex)) { - if (!root.isEmpty()) { - return root; - } else { - return firstEntry(); - } - } else if (KeyAnalyzer.isEqualBitKey(bitIndex)) { - return found; - } - - // we should have exited above. - throw new IllegalStateException("invalid lookup: " + key); - } - - /** - * Returns a key-value mapping associated with the greatest key - * strictly less than the given key, or null if there is no such key. - */ - TrieEntry lowerEntry(final K key) { - // Basically: - // Follow the steps of adding an entry, but instead... - // - // - If we ever encounter a situation where we found an equal - // key, we return it's previousEntry immediately. - // - // - If we hit root (empty or not), return null. - // - // - If we have to add a new item, we temporarily add it, - // find the previousEntry to it, then remove the added item. - // - // These steps ensure that the returned value is always just before - // the key or null (if there was nothing before it). - - // TODO: Cleanup so that we don't actually have to add/remove from the - // tree. (We do it here because there are other well-defined - // functions to perform the search.) - final int lengthInBits = lengthInBits(key); - - if (lengthInBits == 0) { - return null; // there can never be anything before root. - } - - final TrieEntry found = getNearestEntryForKey(key, lengthInBits); - if (compareKeys(key, found.key)) { - return previousEntry(found); - } - - final int bitIndex = bitIndex(key, found.key); - if (KeyAnalyzer.isValidBitIndex(bitIndex)) { - final TrieEntry added = new TrieEntry(key, null, bitIndex); - addEntry(added, lengthInBits); - incrementSize(); // must increment because remove will decrement - final TrieEntry prior = previousEntry(added); - removeEntry(added); - modCount -= 2; // we didn't really modify it. - return prior; - } else if (KeyAnalyzer.isNullBitKey(bitIndex)) { - return null; - } else if (KeyAnalyzer.isEqualBitKey(bitIndex)) { - return previousEntry(found); - } - - // we should have exited above. - throw new IllegalStateException("invalid lookup: " + key); - } - - /** - * Returns a key-value mapping associated with the greatest key - * less than or equal to the given key, or null if there is no such key. - */ - TrieEntry floorEntry(final K key) { - // TODO: Cleanup so that we don't actually have to add/remove from the - // tree. (We do it here because there are other well-defined - // functions to perform the search.) - final int lengthInBits = lengthInBits(key); - - if (lengthInBits == 0) { - if (!root.isEmpty()) { - return root; - } else { - return null; - } - } - - final TrieEntry found = getNearestEntryForKey(key, lengthInBits); - if (compareKeys(key, found.key)) { - return found; - } - - final int bitIndex = bitIndex(key, found.key); - if (KeyAnalyzer.isValidBitIndex(bitIndex)) { - final TrieEntry added = new TrieEntry(key, null, bitIndex); - addEntry(added, lengthInBits); - incrementSize(); // must increment because remove will decrement - final TrieEntry floor = previousEntry(added); - removeEntry(added); - modCount -= 2; // we didn't really modify it. - return floor; - } else if (KeyAnalyzer.isNullBitKey(bitIndex)) { - if (!root.isEmpty()) { - return root; - } else { - return null; - } - } else if (KeyAnalyzer.isEqualBitKey(bitIndex)) { - return found; - } - - // we should have exited above. - throw new IllegalStateException("invalid lookup: " + key); - } - - /** - * Finds the subtree that contains the prefix. - * - * This is very similar to getR but with the difference that - * we stop the lookup if h.bitIndex > lengthInBits. - */ - TrieEntry subtree(final K prefix, final int offsetInBits, final int lengthInBits) { - TrieEntry current = root.left; - TrieEntry path = root; - while(true) { - if (current.bitIndex <= path.bitIndex || lengthInBits < current.bitIndex) { - break; - } - - path = current; - if (!isBitSet(prefix, offsetInBits + current.bitIndex, offsetInBits + lengthInBits)) { - current = current.left; - } else { - current = current.right; - } - } - - // Make sure the entry is valid for a subtree. - final TrieEntry entry = current.isEmpty() ? path : current; - - // If entry is root, it can't be empty. - if (entry.isEmpty()) { - return null; - } - - final int endIndexInBits = offsetInBits + lengthInBits; - - // if root && length of root is less than length of lookup, - // there's nothing. - // (this prevents returning the whole subtree if root has an empty - // string and we want to lookup things with "\0") - if (entry == root && lengthInBits(entry.getKey()) < endIndexInBits) { - return null; - } - - // Found key's length-th bit differs from our key - // which means it cannot be the prefix... - if (isBitSet(prefix, endIndexInBits, endIndexInBits) - != isBitSet(entry.key, lengthInBits, lengthInBits(entry.key))) { - return null; - } - - // ... or there are less than 'length' equal bits - final int bitIndex = keyAnalyzer.bitIndex(prefix, offsetInBits, - lengthInBits, entry.key, 0, lengthInBits(entry.getKey())); - - if (bitIndex >= 0 && bitIndex < lengthInBits) { - return null; - } - - return entry; - } - - /** - * Returns the last entry the {@link Trie} is storing. - * - *

This is implemented by going always to the right until - * we encounter a valid uplink. That uplink is the last key. - */ - TrieEntry lastEntry() { - return followRight(root.left); - } - - /** - * Traverses down the right path until it finds an uplink. - */ - TrieEntry followRight(TrieEntry node) { - // if Trie is empty, no last entry. - if (node.right == null) { - return null; - } - - // Go as far right as possible, until we encounter an uplink. - while (node.right.bitIndex > node.bitIndex) { - node = node.right; - } - - return node.right; - } - - /** - * Returns the node lexicographically before the given node (or null if none). - * - * This follows four simple branches: - * - If the uplink that returned us was a right uplink: - * - If predecessor's left is a valid uplink from predecessor, return it. - * - Else, follow the right path from the predecessor's left. - * - If the uplink that returned us was a left uplink: - * - Loop back through parents until we encounter a node where - * node != node.parent.left. - * - If node.parent.left is uplink from node.parent: - * - If node.parent.left is not root, return it. - * - If it is root & root isEmpty, return null. - * - If it is root & root !isEmpty, return root. - * - If node.parent.left is not uplink from node.parent: - * - Follow right path for first right child from node.parent.left - * - * @param start the start entry - */ - TrieEntry previousEntry(final TrieEntry start) { - if (start.predecessor == null) { - throw new IllegalArgumentException("must have come from somewhere!"); - } - - if (start.predecessor.right == start) { - if (isValidUplink(start.predecessor.left, start.predecessor)) { - return start.predecessor.left; - } else { - return followRight(start.predecessor.left); - } - } else { - TrieEntry node = start.predecessor; - while (node.parent != null && node == node.parent.left) { - node = node.parent; - } - - if (node.parent == null) { // can be null if we're looking up root. - return null; - } - - if (isValidUplink(node.parent.left, node.parent)) { - if (node.parent.left == root) { - if (root.isEmpty()) { - return null; - } else { - return root; - } - - } else { - return node.parent.left; - } - } else { - return followRight(node.parent.left); - } - } - } - - /** - * Returns the entry lexicographically after the given entry. - * If the given entry is null, returns the first node. - * - * This will traverse only within the subtree. If the given node - * is not within the subtree, this will have undefined results. - */ - TrieEntry nextEntryInSubtree(final TrieEntry node, - final TrieEntry parentOfSubtree) { - if (node == null) { - return firstEntry(); - } else { - return nextEntryImpl(node.predecessor, node, parentOfSubtree); - } - } - - /** - * A range view of the {@link Trie}. - */ - private abstract class RangeMap extends AbstractMap - implements SortedMap { - - /** The {@link #entrySet()} view. */ - private transient volatile Set> entrySet; - - /** - * Creates and returns an {@link #entrySet()} view of the {@link RangeMap}. - */ - protected abstract Set> createEntrySet(); - - /** - * Returns the FROM Key. - */ - protected abstract K getFromKey(); - - /** - * Whether or not the {@link #getFromKey()} is in the range. - */ - protected abstract boolean isFromInclusive(); - - /** - * Returns the TO Key. - */ - protected abstract K getToKey(); - - /** - * Whether or not the {@link #getToKey()} is in the range. - */ - protected abstract boolean isToInclusive(); - - public Comparator comparator() { - return PatriciaTrie.this.comparator(); - } - - @Override - public boolean containsKey(final Object key) { - if (!inRange(castKey(key))) { - return false; - } - - return PatriciaTrie.this.containsKey(key); - } - - @Override - public V remove(final Object key) { - if (!inRange(castKey(key))) { - return null; - } - - return PatriciaTrie.this.remove(key); - } - - @Override - public V get(final Object key) { - if (!inRange(castKey(key))) { - return null; - } - - return PatriciaTrie.this.get(key); - } - - @Override - public V put(final K key, final V value) { - if (!inRange(key)) { - throw new IllegalArgumentException("Key is out of range: " + key); - } - return PatriciaTrie.this.put(key, value); - } - - @Override - public Set> entrySet() { - if (entrySet == null) { - entrySet = createEntrySet(); - } - return entrySet; - } - - public SortedMap subMap(final K fromKey, final K toKey) { - if (!inRange2(fromKey)) { - throw new IllegalArgumentException("FromKey is out of range: " + fromKey); - } - - if (!inRange2(toKey)) { - throw new IllegalArgumentException("ToKey is out of range: " + toKey); - } - - return createRangeMap(fromKey, isFromInclusive(), toKey, isToInclusive()); - } - - public SortedMap headMap(final K toKey) { - if (!inRange2(toKey)) { - throw new IllegalArgumentException("ToKey is out of range: " + toKey); - } - return createRangeMap(getFromKey(), isFromInclusive(), toKey, isToInclusive()); - } - - public SortedMap tailMap(final K fromKey) { - if (!inRange2(fromKey)) { - throw new IllegalArgumentException("FromKey is out of range: " + fromKey); - } - return createRangeMap(fromKey, isFromInclusive(), getToKey(), isToInclusive()); - } - - /** - * Returns true if the provided key is greater than TO and less than FROM. - */ - protected boolean inRange(final K key) { - final K fromKey = getFromKey(); - final K toKey = getToKey(); - - return (fromKey == null || inFromRange(key, false)) && (toKey == null || inToRange(key, false)); - } - - /** - * This form allows the high endpoint (as well as all legit keys). - */ - protected boolean inRange2(final K key) { - final K fromKey = getFromKey(); - final K toKey = getToKey(); - - return (fromKey == null || inFromRange(key, false)) && (toKey == null || inToRange(key, true)); - } - - /** - * Returns true if the provided key is in the FROM range of the {@link RangeMap}. - */ - protected boolean inFromRange(final K key, final boolean forceInclusive) { - final K fromKey = getFromKey(); - final boolean fromInclusive = isFromInclusive(); - - final int ret = keyAnalyzer.compare(key, fromKey); - if (fromInclusive || forceInclusive) { - return ret >= 0; - } else { - return ret > 0; - } - } - - /** - * Returns true if the provided key is in the TO range of the {@link RangeMap}. - */ - protected boolean inToRange(final K key, final boolean forceInclusive) { - final K toKey = getToKey(); - final boolean toInclusive = isToInclusive(); - - final int ret = keyAnalyzer.compare(key, toKey); - if (toInclusive || forceInclusive) { - return ret <= 0; - } else { - return ret < 0; - } - } - - /** - * Creates and returns a sub-range view of the current {@link RangeMap}. - */ - protected abstract SortedMap createRangeMap(K fromKey, boolean fromInclusive, - K toKey, boolean toInclusive); - } - - /** - * A {@link RangeMap} that deals with {@link Entry}s. - */ - private class RangeEntryMap extends RangeMap { - - /** The key to start from, null if the beginning. */ - private final K fromKey; - - /** The key to end at, null if till the end. */ - private final K toKey; - - /** Whether or not the 'from' is inclusive. */ - private final boolean fromInclusive; - - /** Whether or not the 'to' is inclusive. */ - private final boolean toInclusive; - - /** - * Creates a {@link RangeEntryMap} with the fromKey included and - * the toKey excluded from the range. - */ - protected RangeEntryMap(final K fromKey, final K toKey) { - this(fromKey, true, toKey, false); - } - - /** - * Creates a {@link RangeEntryMap}. - */ - protected RangeEntryMap(final K fromKey, final boolean fromInclusive, - final K toKey, final boolean toInclusive) { - - if (fromKey == null && toKey == null) { - throw new IllegalArgumentException("must have a from or to!"); - } - - if (fromKey != null && toKey != null - && keyAnalyzer.compare(fromKey, toKey) > 0) { - throw new IllegalArgumentException("fromKey > toKey"); - } - - this.fromKey = fromKey; - this.fromInclusive = fromInclusive; - this.toKey = toKey; - this.toInclusive = toInclusive; - } - - public K firstKey() { - Map.Entry e = null; - if (fromKey == null) { - e = firstEntry(); - } else { - if (fromInclusive) { - e = ceilingEntry(fromKey); - } else { - e = higherEntry(fromKey); - } - } - - final K first = e != null ? e.getKey() : null; - if (e == null || toKey != null && !inToRange(first, false)) { - throw new NoSuchElementException(); - } - return first; - } - - public K lastKey() { - Map.Entry e; - if (toKey == null) { - e = lastEntry(); - } else { - if (toInclusive) { - e = floorEntry(toKey); - } else { - e = lowerEntry(toKey); - } - } - - final K last = e != null ? e.getKey() : null; - if (e == null || fromKey != null && !inFromRange(last, false)) { - throw new NoSuchElementException(); - } - return last; - } - - @Override - protected Set> createEntrySet() { - return new RangeEntrySet(this); - } - - @Override - public K getFromKey() { - return fromKey; - } - - @Override - public K getToKey() { - return toKey; - } - - @Override - public boolean isFromInclusive() { - return fromInclusive; - } - - @Override - public boolean isToInclusive() { - return toInclusive; - } - - @Override - protected SortedMap createRangeMap(final K fromKey, final boolean fromInclusive, - final K toKey, final boolean toInclusive) { - return new RangeEntryMap(fromKey, fromInclusive, toKey, toInclusive); - } - } - - /** - * A {@link Set} view of a {@link RangeMap}. - */ - private class RangeEntrySet extends AbstractSet> { - - private final RangeMap delegate; - - private transient int size = -1; - - private transient int expectedModCount; - - /** - * Creates a {@link RangeEntrySet}. - */ - public RangeEntrySet(final RangeMap delegate) { - if (delegate == null) { - throw new NullPointerException("delegate"); - } - - this.delegate = delegate; - } - - @Override - public Iterator> iterator() { - final K fromKey = delegate.getFromKey(); - final K toKey = delegate.getToKey(); - - TrieEntry first = null; - if (fromKey == null) { - first = firstEntry(); - } else { - first = ceilingEntry(fromKey); - } - - TrieEntry last = null; - if (toKey != null) { - last = ceilingEntry(toKey); - } - - return new EntryIterator(first, last); - } - - @Override - public int size() { - if (size == -1 || expectedModCount != PatriciaTrie.this.modCount) { - size = 0; - - for (final Iterator it = iterator(); it.hasNext(); it.next()) { - ++size; - } - - expectedModCount = PatriciaTrie.this.modCount; - } - return size; - } - - @Override - public boolean isEmpty() { - return !iterator().hasNext(); - } - - @SuppressWarnings("unchecked") - @Override - public boolean contains(final Object o) { - if (!(o instanceof Map.Entry)) { - return false; - } - - final Map.Entry entry = (Map.Entry) o; - final K key = entry.getKey(); - if (!delegate.inRange(key)) { - return false; - } - - final TrieEntry node = getEntry(key); - return node != null && compare(node.getValue(), entry.getValue()); - } - - @SuppressWarnings("unchecked") - @Override - public boolean remove(final Object o) { - if (!(o instanceof Map.Entry)) { - return false; - } - - final Map.Entry entry = (Map.Entry) o; - final K key = entry.getKey(); - if (!delegate.inRange(key)) { - return false; - } - - final TrieEntry node = getEntry(key); - if (node != null && compare(node.getValue(), entry.getValue())) { - removeEntry(node); - return true; - } - return false; - } - - /** - * An {@link Iterator} for {@link RangeEntrySet}s. - */ - private final class EntryIterator extends TrieIterator> { - - private final K excludedKey; - - /** - * Creates a {@link EntryIterator}. - */ - private EntryIterator(final TrieEntry first, final TrieEntry last) { - super(first); - this.excludedKey = last != null ? last.getKey() : null; - } - - @Override - public boolean hasNext() { - return next != null && !compare(next.key, excludedKey); - } - - public Map.Entry next() { - if (next == null || compare(next.key, excludedKey)) { - throw new NoSuchElementException(); - } - return nextEntry(); - } - } - } - - /** - * A submap used for prefix views over the {@link Trie}. - */ - private class PrefixRangeMap extends RangeMap { - - private final K prefix; - - private final int offsetInBits; - - private final int lengthInBits; - - private K fromKey = null; - - private K toKey = null; - - private transient int expectedModCount = 0; - - private int size = -1; - - /** - * Creates a {@link PrefixRangeMap}. - */ - private PrefixRangeMap(final K prefix, final int offsetInBits, final int lengthInBits) { - this.prefix = prefix; - this.offsetInBits = offsetInBits; - this.lengthInBits = lengthInBits; - } - - /** - * This method does two things. It determinates the FROM - * and TO range of the {@link PrefixRangeMap} and the number - * of elements in the range. This method must be called every - * time the {@link Trie} has changed. - */ - private int fixup() { - // The trie has changed since we last - // found our toKey / fromKey - if (size == - 1 || PatriciaTrie.this.modCount != expectedModCount) { - final Iterator> it = entrySet().iterator(); - size = 0; - - Map.Entry entry = null; - if (it.hasNext()) { - entry = it.next(); - size = 1; - } - - fromKey = entry == null ? null : entry.getKey(); - if (fromKey != null) { - final TrieEntry prior = previousEntry((TrieEntry)entry); - fromKey = prior == null ? null : prior.getKey(); - } - - toKey = fromKey; - - while (it.hasNext()) { - ++size; - entry = it.next(); - } - - toKey = entry == null ? null : entry.getKey(); - - if (toKey != null) { - entry = nextEntry((TrieEntry)entry); - toKey = entry == null ? null : entry.getKey(); - } - - expectedModCount = PatriciaTrie.this.modCount; - } - - return size; - } - - public K firstKey() { - fixup(); - - Map.Entry e = null; - if (fromKey == null) { - e = firstEntry(); - } else { - e = higherEntry(fromKey); - } - - final K first = e != null ? e.getKey() : null; - if (e == null || !keyAnalyzer.isPrefix(prefix, - offsetInBits, lengthInBits, first)) { - throw new NoSuchElementException(); - } - - return first; - } - - public K lastKey() { - fixup(); - - Map.Entry e = null; - if (toKey == null) { - e = lastEntry(); - } else { - e = lowerEntry(toKey); - } - - final K last = e != null ? e.getKey() : null; - if (e == null || !keyAnalyzer.isPrefix(prefix, - offsetInBits, lengthInBits, last)) { - throw new NoSuchElementException(); - } - - return last; - } - - /** - * Returns true if this {@link PrefixRangeMap}'s key is a prefix of the provided key. - */ - @Override - protected boolean inRange(final K key) { - return keyAnalyzer.isPrefix(prefix, offsetInBits, lengthInBits, key); - } - - /** - * Same as {@link #inRange(Object)}. - */ - @Override - protected boolean inRange2(final K key) { - return inRange(key); - } - - /** - * Returns true if the provided Key is in the FROM range of the {@link PrefixRangeMap}. - */ - @Override - protected boolean inFromRange(final K key, final boolean forceInclusive) { - return keyAnalyzer.isPrefix(prefix, offsetInBits, lengthInBits, key); - } - - /** - * Returns true if the provided Key is in the TO range of the {@link PrefixRangeMap}. - */ - @Override - protected boolean inToRange(final K key, final boolean forceInclusive) { - return keyAnalyzer.isPrefix(prefix, offsetInBits, lengthInBits, key); - } - - @Override - protected Set> createEntrySet() { - return new PrefixRangeEntrySet(this); - } - - @Override - public K getFromKey() { - return fromKey; - } - - @Override - public K getToKey() { - return toKey; - } - - @Override - public boolean isFromInclusive() { - return false; - } - - @Override - public boolean isToInclusive() { - return false; - } - - @Override - protected SortedMap createRangeMap(final K fromKey, final boolean fromInclusive, - final K toKey, final boolean toInclusive) { - return new RangeEntryMap(fromKey, fromInclusive, toKey, toInclusive); - } - } - - /** - * A prefix {@link RangeEntrySet} view of the {@link Trie}. - */ - private final class PrefixRangeEntrySet extends RangeEntrySet { - - private final PrefixRangeMap delegate; - - private TrieEntry prefixStart; - - private int expectedModCount = 0; - - /** - * Creates a {@link PrefixRangeEntrySet}. - */ - public PrefixRangeEntrySet(final PrefixRangeMap delegate) { - super(delegate); - this.delegate = delegate; - } - - @Override - public int size() { - return delegate.fixup(); - } - - @Override - public Iterator> iterator() { - if (PatriciaTrie.this.modCount != expectedModCount) { - prefixStart = subtree(delegate.prefix, delegate.offsetInBits, delegate.lengthInBits); - expectedModCount = PatriciaTrie.this.modCount; - } - - if (prefixStart == null) { - final Set> empty = Collections.emptySet(); - return empty.iterator(); - } else if (delegate.lengthInBits >= prefixStart.bitIndex) { - return new SingletonIterator(prefixStart); - } else { - return new EntryIterator(prefixStart, delegate.prefix, delegate.offsetInBits, delegate.lengthInBits); - } - } - - /** - * An {@link Iterator} that holds a single {@link TrieEntry}. - */ - private final class SingletonIterator implements Iterator> { - - private final TrieEntry entry; - - private int hit = 0; - - public SingletonIterator(final TrieEntry entry) { - this.entry = entry; - } - - public boolean hasNext() { - return hit == 0; - } - - public Map.Entry next() { - if (hit != 0) { - throw new NoSuchElementException(); - } - - ++hit; - return entry; - } - - public void remove() { - if (hit != 1) { - throw new IllegalStateException(); - } - - ++hit; - PatriciaTrie.this.removeEntry(entry); - } - } - - /** - * An {@link Iterator} for iterating over a prefix search. - */ - private final class EntryIterator extends TrieIterator> { - - // values to reset the subtree if we remove it. - private final K prefix; - private final int offset; - private final int lengthInBits; - private boolean lastOne; - - private TrieEntry subtree; // the subtree to search within - - /** - * Starts iteration at the given entry & search only - * within the given subtree. - */ - EntryIterator(final TrieEntry startScan, final K prefix, - final int offset, final int lengthInBits) { - subtree = startScan; - next = PatriciaTrie.this.followLeft(startScan); - this.prefix = prefix; - this.offset = offset; - this.lengthInBits = lengthInBits; - } - - public Map.Entry next() { - final Map.Entry entry = nextEntry(); - if (lastOne) { - next = null; - } - return entry; - } - - @Override - protected TrieEntry findNext(final TrieEntry prior) { - return PatriciaTrie.this.nextEntryInSubtree(prior, subtree); - } - - @Override - public void remove() { - // If the current entry we're removing is the subtree - // then we need to find a new subtree parent. - boolean needsFixing = false; - final int bitIdx = subtree.bitIndex; - if (current == subtree) { - needsFixing = true; - } - - super.remove(); - - // If the subtree changed its bitIndex or we - // removed the old subtree, get a new one. - if (bitIdx != subtree.bitIndex || needsFixing) { - subtree = subtree(prefix, offset, lengthInBits); - } - - // If the subtree's bitIndex is less than the - // length of our prefix, it's the last item - // in the prefix tree. - if (lengthInBits >= subtree.bitIndex) { - lastOne = true; - } - } - } - } } diff --git a/src/main/java/org/apache/commons/collections4/trie/PatriciaTrieBase.java b/src/main/java/org/apache/commons/collections4/trie/PatriciaTrieBase.java deleted file mode 100644 index c6d84e23b..000000000 --- a/src/main/java/org/apache/commons/collections4/trie/PatriciaTrieBase.java +++ /dev/null @@ -1,1122 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 - * - * 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 org.apache.commons.collections4.trie; - -import java.util.AbstractCollection; -import java.util.AbstractSet; -import java.util.Collection; -import java.util.ConcurrentModificationException; -import java.util.Iterator; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; - -import org.apache.commons.collections4.Trie.Cursor.Decision; - -/** - * This class implements the base PATRICIA algorithm and everything that - * is related to the {@link Map} interface. - * - * @since 4.0 - * @version $Id$ - */ -abstract class PatriciaTrieBase extends AbstractBitwiseTrie { - - private static final long serialVersionUID = 5155253417231339498L; - - /** - * The root node of the {@link Trie}. - */ - final TrieEntry root = new TrieEntry(null, null, -1); - - /** - * Each of these fields are initialized to contain an instance of the - * appropriate view the first time this view is requested. The views are - * stateless, so there's no reason to create more than one of each. - */ - private transient volatile Set keySet; - private transient volatile Collection values; - private transient volatile Set> entrySet; - - /** The current size of the {@link Trie}. */ - private int size = 0; - - /** - * The number of times this {@link Trie} has been modified. - * It's used to detect concurrent modifications and fail-fast the {@link Iterator}s. - */ - protected transient int modCount = 0; - - public PatriciaTrieBase(final KeyAnalyzer keyAnalyzer) { - super(keyAnalyzer); - } - - /** - * Constructs a new {@link org.apache.commons.collections4.Trie Trie} using the given - * {@link KeyAnalyzer} and initializes the {@link org.apache.commons.collections4.Trie Trie} - * with the values from the provided {@link Map}. - */ - public PatriciaTrieBase(final KeyAnalyzer keyAnalyzer, final Map m) { - super(keyAnalyzer); - putAll(m); - } - - @Override - public void clear() { - root.key = null; - root.bitIndex = -1; - root.value = null; - - root.parent = null; - root.left = root; - root.right = null; - root.predecessor = root; - - size = 0; - incrementModCount(); - } - - @Override - public int size() { - return size; - } - - /** - * A helper method to increment the {@link Trie} size and the modification counter. - */ - void incrementSize() { - size++; - incrementModCount(); - } - - /** - * A helper method to decrement the {@link Trie} size and increment the modification counter. - */ - void decrementSize() { - size--; - incrementModCount(); - } - - /** - * A helper method to increment the modification counter. - */ - private void incrementModCount() { - ++modCount; - } - - @Override - public V put(final K key, final V value) { - if (key == null) { - throw new NullPointerException("Key cannot be null"); - } - - final int lengthInBits = lengthInBits(key); - - // The only place to store a key with a length - // of zero bits is the root node - if (lengthInBits == 0) { - if (root.isEmpty()) { - incrementSize(); - } else { - incrementModCount(); - } - return root.setKeyValue(key, value); - } - - final TrieEntry found = getNearestEntryForKey(key, lengthInBits); - if (compareKeys(key, found.key)) { - if (found.isEmpty()) { // <- must be the root - incrementSize(); - } else { - incrementModCount(); - } - return found.setKeyValue(key, value); - } - - final int bitIndex = bitIndex(key, found.key); - if (!KeyAnalyzer.isOutOfBoundsIndex(bitIndex)) { - if (KeyAnalyzer.isValidBitIndex(bitIndex)) { // in 99.999...9% the case - /* NEW KEY+VALUE TUPLE */ - final TrieEntry t = new TrieEntry(key, value, bitIndex); - addEntry(t, lengthInBits); - incrementSize(); - return null; - } else if (KeyAnalyzer.isNullBitKey(bitIndex)) { - // A bits of the Key are zero. The only place to - // store such a Key is the root Node! - - /* NULL BIT KEY */ - if (root.isEmpty()) { - incrementSize(); - } else { - incrementModCount(); - } - return root.setKeyValue(key, value); - - } else if (KeyAnalyzer.isEqualBitKey(bitIndex)) { - // This is a very special and rare case. - - /* REPLACE OLD KEY+VALUE */ - if (found != root) { - incrementModCount(); - return found.setKeyValue(key, value); - } - } - } - - throw new IllegalArgumentException("Failed to put: " + key + " -> " + value + ", " + bitIndex); - } - - /** - * Adds the given {@link TrieEntry} to the {@link Trie}. - */ - TrieEntry addEntry(final TrieEntry entry, final int lengthInBits) { - TrieEntry current = root.left; - TrieEntry path = root; - while(true) { - if (current.bitIndex >= entry.bitIndex - || current.bitIndex <= path.bitIndex) { - entry.predecessor = entry; - - if (!isBitSet(entry.key, entry.bitIndex, lengthInBits)) { - entry.left = entry; - entry.right = current; - } else { - entry.left = current; - entry.right = entry; - } - - entry.parent = path; - if (current.bitIndex >= entry.bitIndex) { - current.parent = entry; - } - - // if we inserted an uplink, set the predecessor on it - if (current.bitIndex <= path.bitIndex) { - current.predecessor = entry; - } - - if (path == root || !isBitSet(entry.key, path.bitIndex, lengthInBits)) { - path.left = entry; - } else { - path.right = entry; - } - - return entry; - } - - path = current; - - if (!isBitSet(entry.key, current.bitIndex, lengthInBits)) { - current = current.left; - } else { - current = current.right; - } - } - } - - @Override - public V get(final Object k) { - final TrieEntry entry = getEntry(k); - return entry != null ? entry.getValue() : null; - } - - /** - * Returns the entry associated with the specified key in the - * PatriciaTrieBase. Returns null if the map contains no mapping - * for this key. - *

- * This may throw ClassCastException if the object is not of type K. - */ - TrieEntry getEntry(final Object k) { - final K key = castKey(k); - if (key == null) { - return null; - } - - final int lengthInBits = lengthInBits(key); - final TrieEntry entry = getNearestEntryForKey(key, lengthInBits); - return !entry.isEmpty() && compareKeys(key, entry.key) ? entry : null; - } - - public Map.Entry select(final K key) { - final int lengthInBits = lengthInBits(key); - final Reference> reference = new Reference>(); - if (!selectR(root.left, -1, key, lengthInBits, reference)) { - return reference.get(); - } - return null; - } - - public Map.Entry select(final K key, final Cursor cursor) { - final int lengthInBits = lengthInBits(key); - final Reference> reference = new Reference>(); - selectR(root.left, -1, key, lengthInBits, cursor, reference); - return reference.get(); - } - - /** - * This is equivalent to the other {@link #selectR(TrieEntry, int, Object, int, Cursor, Reference)} - * method but without its overhead because we're selecting only one best matching Entry from the {@link Trie}. - */ - private boolean selectR(final TrieEntry h, final int bitIndex, - final K key, final int lengthInBits, - final Reference> reference) { - - if (h.bitIndex <= bitIndex) { - // If we hit the root Node and it is empty - // we have to look for an alternative best - // matching node. - if (!h.isEmpty()) { - reference.set(h); - return false; - } - return true; - } - - if (!isBitSet(key, h.bitIndex, lengthInBits)) { - if (selectR(h.left, h.bitIndex, key, lengthInBits, reference)) { - return selectR(h.right, h.bitIndex, key, lengthInBits, reference); - } - } else { - if (selectR(h.right, h.bitIndex, key, lengthInBits, reference)) { - return selectR(h.left, h.bitIndex, key, lengthInBits, reference); - } - } - return false; - } - - private boolean selectR(final TrieEntry h, final int bitIndex, - final K key, - final int lengthInBits, - final Cursor cursor, - final Reference> reference) { - - if (h.bitIndex <= bitIndex) { - if (!h.isEmpty()) { - final Decision decision = cursor.select(h); - switch(decision) { - case REMOVE: - throw new UnsupportedOperationException("Cannot remove during select"); - case EXIT: - reference.set(h); - return false; // exit - case REMOVE_AND_EXIT: - final TrieEntry entry = new TrieEntry( - h.getKey(), h.getValue(), -1); - reference.set(entry); - removeEntry(h); - return false; - case CONTINUE: - // fall through. - default: - break; - } - } - return true; // continue - } - - if (!isBitSet(key, h.bitIndex, lengthInBits)) { - if (selectR(h.left, h.bitIndex, key, lengthInBits, cursor, reference)) { - return selectR(h.right, h.bitIndex, key, lengthInBits, cursor, reference); - } - } else { - if (selectR(h.right, h.bitIndex, key, lengthInBits, cursor, reference)) { - return selectR(h.left, h.bitIndex, key, lengthInBits, cursor, reference); - } - } - - return false; - } - - public Map.Entry traverse(final Cursor cursor) { - TrieEntry entry = nextEntry(null); - while (entry != null) { - final TrieEntry current = entry; - - final Decision decision = cursor.select(current); - entry = nextEntry(current); - - switch(decision) { - case EXIT: - return current; - case REMOVE: - removeEntry(current); - break; // out of switch, stay in while loop - case REMOVE_AND_EXIT: - final Map.Entry value = new TrieEntry( - current.getKey(), current.getValue(), -1); - removeEntry(current); - return value; - case CONTINUE: // do nothing. - default: - break; - } - } - - return null; - } - - @Override - public boolean containsKey(final Object k) { - if (k == null) { - return false; - } - - final K key = castKey(k); - final int lengthInBits = lengthInBits(key); - final TrieEntry entry = getNearestEntryForKey(key, lengthInBits); - return !entry.isEmpty() && compareKeys(key, entry.key); - } - - @Override - public Set> entrySet() { - if (entrySet == null) { - entrySet = new EntrySet(); - } - return entrySet; - } - - @Override - public Set keySet() { - if (keySet == null) { - keySet = new KeySet(); - } - return keySet; - } - - @Override - public Collection values() { - if (values == null) { - values = new Values(); - } - return values; - } - - /** - * {@inheritDoc} - * - * @throws ClassCastException if provided key is of an incompatible type - */ - @Override - public V remove(final Object k) { - if (k == null) { - return null; - } - - final K key = castKey(k); - final int lengthInBits = lengthInBits(key); - TrieEntry current = root.left; - TrieEntry path = root; - while (true) { - if (current.bitIndex <= path.bitIndex) { - if (!current.isEmpty() && compareKeys(key, current.key)) { - return removeEntry(current); - } else { - return null; - } - } - - path = current; - - if (!isBitSet(key, current.bitIndex, lengthInBits)) { - current = current.left; - } else { - current = current.right; - } - } - } - - /** - * Returns the nearest entry for a given key. This is useful - * for finding knowing if a given key exists (and finding the value - * for it), or for inserting the key. - * - * The actual get implementation. This is very similar to - * selectR but with the exception that it might return the - * root Entry even if it's empty. - */ - TrieEntry getNearestEntryForKey(final K key, final int lengthInBits) { - TrieEntry current = root.left; - TrieEntry path = root; - while(true) { - if (current.bitIndex <= path.bitIndex) { - return current; - } - - path = current; - if (!isBitSet(key, current.bitIndex, lengthInBits)) { - current = current.left; - } else { - current = current.right; - } - } - } - - /** - * Removes a single entry from the {@link Trie}. - * - * If we found a Key (Entry h) then figure out if it's - * an internal (hard to remove) or external Entry (easy - * to remove) - */ - V removeEntry(final TrieEntry h) { - if (h != root) { - if (h.isInternalNode()) { - removeInternalEntry(h); - } else { - removeExternalEntry(h); - } - } - - decrementSize(); - return h.setKeyValue(null, null); - } - - /** - * Removes an external entry from the {@link Trie}. - * - * If it's an external Entry then just remove it. - * This is very easy and straight forward. - */ - private void removeExternalEntry(final TrieEntry h) { - if (h == root) { - throw new IllegalArgumentException("Cannot delete root Entry!"); - } else if (!h.isExternalNode()) { - throw new IllegalArgumentException(h + " is not an external Entry!"); - } - - final TrieEntry parent = h.parent; - final TrieEntry child = h.left == h ? h.right : h.left; - - if (parent.left == h) { - parent.left = child; - } else { - parent.right = child; - } - - // either the parent is changing, or the predecessor is changing. - if (child.bitIndex > parent.bitIndex) { - child.parent = parent; - } else { - child.predecessor = parent; - } - - } - - /** - * Removes an internal entry from the {@link Trie}. - * - * If it's an internal Entry then "good luck" with understanding - * this code. The Idea is essentially that Entry p takes Entry h's - * place in the trie which requires some re-wiring. - */ - private void removeInternalEntry(final TrieEntry h) { - if (h == root) { - throw new IllegalArgumentException("Cannot delete root Entry!"); - } else if (!h.isInternalNode()) { - throw new IllegalArgumentException(h + " is not an internal Entry!"); - } - - final TrieEntry p = h.predecessor; - - // Set P's bitIndex - p.bitIndex = h.bitIndex; - - // Fix P's parent, predecessor and child Nodes - { - final TrieEntry parent = p.parent; - final TrieEntry child = p.left == h ? p.right : p.left; - - // if it was looping to itself previously, - // it will now be pointed from it's parent - // (if we aren't removing it's parent -- - // in that case, it remains looping to itself). - // otherwise, it will continue to have the same - // predecessor. - if (p.predecessor == p && p.parent != h) { - p.predecessor = p.parent; - } - - if (parent.left == p) { - parent.left = child; - } else { - parent.right = child; - } - - if (child.bitIndex > parent.bitIndex) { - child.parent = parent; - } - } - - // Fix H's parent and child Nodes - { - // If H is a parent of its left and right child - // then change them to P - if (h.left.parent == h) { - h.left.parent = p; - } - - if (h.right.parent == h) { - h.right.parent = p; - } - - // Change H's parent - if (h.parent.left == h) { - h.parent.left = p; - } else { - h.parent.right = p; - } - } - - // Copy the remaining fields from H to P - //p.bitIndex = h.bitIndex; - p.parent = h.parent; - p.left = h.left; - p.right = h.right; - - // Make sure that if h was pointing to any uplinks, - // p now points to them. - if (isValidUplink(p.left, p)) { - p.left.predecessor = p; - } - - if (isValidUplink(p.right, p)) { - p.right.predecessor = p; - } - } - - /** - * Returns the entry lexicographically after the given entry. - * If the given entry is null, returns the first node. - */ - TrieEntry nextEntry(final TrieEntry node) { - if (node == null) { - return firstEntry(); - } else { - return nextEntryImpl(node.predecessor, node, null); - } - } - - /** - * Scans for the next node, starting at the specified point, and using 'previous' - * as a hint that the last node we returned was 'previous' (so we know not to return - * it again). If 'tree' is non-null, this will limit the search to the given tree. - * - * The basic premise is that each iteration can follow the following steps: - * - * 1) Scan all the way to the left. - * a) If we already started from this node last time, proceed to Step 2. - * b) If a valid uplink is found, use it. - * c) If the result is an empty node (root not set), break the scan. - * d) If we already returned the left node, break the scan. - * - * 2) Check the right. - * a) If we already returned the right node, proceed to Step 3. - * b) If it is a valid uplink, use it. - * c) Do Step 1 from the right node. - * - * 3) Back up through the parents until we encounter find a parent - * that we're not the right child of. - * - * 4) If there's no right child of that parent, the iteration is finished. - * Otherwise continue to Step 5. - * - * 5) Check to see if the right child is a valid uplink. - * a) If we already returned that child, proceed to Step 6. - * Otherwise, use it. - * - * 6) If the right child of the parent is the parent itself, we've - * already found & returned the end of the Trie, so exit. - * - * 7) Do Step 1 on the parent's right child. - */ - TrieEntry nextEntryImpl(final TrieEntry start, - final TrieEntry previous, final TrieEntry tree) { - - TrieEntry current = start; - - // Only look at the left if this was a recursive or - // the first check, otherwise we know we've already looked - // at the left. - if (previous == null || start != previous.predecessor) { - while (!current.left.isEmpty()) { - // stop traversing if we've already - // returned the left of this node. - if (previous == current.left) { - break; - } - - if (isValidUplink(current.left, current)) { - return current.left; - } - - current = current.left; - } - } - - // If there's no data at all, exit. - if (current.isEmpty()) { - return null; - } - - // If we've already returned the left, - // and the immediate right is null, - // there's only one entry in the Trie - // which is stored at the root. - // - // / ("") <-- root - // \_/ \ - // null <-- 'current' - // - if (current.right == null) { - return null; - } - - // If nothing valid on the left, try the right. - if (previous != current.right) { - // See if it immediately is valid. - if (isValidUplink(current.right, current)) { - return current.right; - } - - // Must search on the right's side if it wasn't initially valid. - return nextEntryImpl(current.right, previous, tree); - } - - // Neither left nor right are valid, find the first parent - // whose child did not come from the right & traverse it. - while (current == current.parent.right) { - // If we're going to traverse to above the subtree, stop. - if (current == tree) { - return null; - } - - current = current.parent; - } - - // If we're on the top of the subtree, we can't go any higher. - if (current == tree) { - return null; - } - - // If there's no right, the parent must be root, so we're done. - if (current.parent.right == null) { - return null; - } - - // If the parent's right points to itself, we've found one. - if (previous != current.parent.right - && isValidUplink(current.parent.right, current.parent)) { - return current.parent.right; - } - - // If the parent's right is itself, there can't be any more nodes. - if (current.parent.right == current.parent) { - return null; - } - - // We need to traverse down the parent's right's path. - return nextEntryImpl(current.parent.right, previous, tree); - } - - /** - * Returns the first entry the {@link Trie} is storing. - *

- * This is implemented by going always to the left until - * we encounter a valid uplink. That uplink is the first key. - */ - TrieEntry firstEntry() { - // if Trie is empty, no first node. - if (isEmpty()) { - return null; - } - - return followLeft(root); - } - - /** - * Goes left through the tree until it finds a valid node. - */ - TrieEntry followLeft(TrieEntry node) { - while(true) { - TrieEntry child = node.left; - // if we hit root and it didn't have a node, go right instead. - if (child.isEmpty()) { - child = node.right; - } - - if (child.bitIndex <= node.bitIndex) { - return child; - } - - node = child; - } - } - - /** - * Returns true if 'next' is a valid uplink coming from 'from'. - */ - static boolean isValidUplink(final TrieEntry next, final TrieEntry from) { - return next != null && next.bitIndex <= from.bitIndex && !next.isEmpty(); - } - - /** - * A {@link Reference} allows us to return something through a Method's - * argument list. An alternative would be to an Array with a length of - * one (1) but that leads to compiler warnings. Computationally and memory - * wise there's no difference (except for the need to load the - * {@link Reference} Class but that happens only once). - */ - private static class Reference { - - private E item; - - public void set(final E item) { - this.item = item; - } - - public E get() { - return item; - } - } - - /** - * A {@link Trie} is a set of {@link TrieEntry} nodes. - */ - static class TrieEntry extends BasicEntry { - - private static final long serialVersionUID = 4596023148184140013L; - - /** The index this entry is comparing. */ - protected int bitIndex; - - /** The parent of this entry. */ - protected TrieEntry parent; - - /** The left child of this entry. */ - protected TrieEntry left; - - /** The right child of this entry. */ - protected TrieEntry right; - - /** The entry who uplinks to this entry. */ - protected TrieEntry predecessor; - - public TrieEntry(final K key, final V value, final int bitIndex) { - super(key, value); - - this.bitIndex = bitIndex; - - this.parent = null; - this.left = this; - this.right = null; - this.predecessor = this; - } - - /** - * Whether or not the entry is storing a key. - * Only the root can potentially be empty, all other - * nodes must have a key. - */ - public boolean isEmpty() { - return key == null; - } - - /** - * Neither the left nor right child is a loopback. - */ - public boolean isInternalNode() { - return left != this && right != this; - } - - /** - * Either the left or right child is a loopback. - */ - public boolean isExternalNode() { - return !isInternalNode(); - } - - @Override - public String toString() { - final StringBuilder buffer = new StringBuilder(); - - if (bitIndex == -1) { - buffer.append("RootEntry("); - } else { - buffer.append("Entry("); - } - - buffer.append("key=").append(getKey()).append(" [").append(bitIndex).append("], "); - buffer.append("value=").append(getValue()).append(", "); - //buffer.append("bitIndex=").append(bitIndex).append(", "); - - if (parent != null) { - if (parent.bitIndex == -1) { - buffer.append("parent=").append("ROOT"); - } else { - buffer.append("parent=").append(parent.getKey()).append(" [").append(parent.bitIndex).append("]"); - } - } else { - buffer.append("parent=").append("null"); - } - buffer.append(", "); - - if (left != null) { - if (left.bitIndex == -1) { - buffer.append("left=").append("ROOT"); - } else { - buffer.append("left=").append(left.getKey()).append(" [").append(left.bitIndex).append("]"); - } - } else { - buffer.append("left=").append("null"); - } - buffer.append(", "); - - if (right != null) { - if (right.bitIndex == -1) { - buffer.append("right=").append("ROOT"); - } else { - buffer.append("right=").append(right.getKey()).append(" [").append(right.bitIndex).append("]"); - } - } else { - buffer.append("right=").append("null"); - } - buffer.append(", "); - - if (predecessor != null) { - if(predecessor.bitIndex == -1) { - buffer.append("predecessor=").append("ROOT"); - } else { - buffer.append("predecessor=").append(predecessor.getKey()).append(" ["). - append(predecessor.bitIndex).append("]"); - } - } - - buffer.append(")"); - return buffer.toString(); - } - } - - - /** - * This is a entry set view of the {@link Trie} as returned by {@link Map#entrySet()}. - */ - private class EntrySet extends AbstractSet> { - - @Override - public Iterator> iterator() { - return new EntryIterator(); - } - - @Override - public boolean contains(final Object o) { - if (!(o instanceof Map.Entry)) { - return false; - } - - final TrieEntry candidate = getEntry(((Map.Entry)o).getKey()); - return candidate != null && candidate.equals(o); - } - - @Override - public boolean remove(final Object o) { - final int size = size(); - PatriciaTrieBase.this.remove(o); - return size != size(); - } - - @Override - public int size() { - return PatriciaTrieBase.this.size(); - } - - @Override - public void clear() { - PatriciaTrieBase.this.clear(); - } - - /** - * An {@link Iterator} that returns {@link Entry} Objects. - */ - private class EntryIterator extends TrieIterator> { - public Map.Entry next() { - return nextEntry(); - } - } - } - - /** - * This is a key set view of the {@link Trie} as returned by {@link Map#keySet()}. - */ - private class KeySet extends AbstractSet { - - @Override - public Iterator iterator() { - return new KeyIterator(); - } - - @Override - public int size() { - return PatriciaTrieBase.this.size(); - } - - @Override - public boolean contains(final Object o) { - return containsKey(o); - } - - @Override - public boolean remove(final Object o) { - final int size = size(); - PatriciaTrieBase.this.remove(o); - return size != size(); - } - - @Override - public void clear() { - PatriciaTrieBase.this.clear(); - } - - /** - * An {@link Iterator} that returns Key Objects. - */ - private class KeyIterator extends TrieIterator { - public K next() { - return nextEntry().getKey(); - } - } - } - - /** - * This is a value view of the {@link Trie} as returned by {@link Map#values()}. - */ - private class Values extends AbstractCollection { - - @Override - public Iterator iterator() { - return new ValueIterator(); - } - - @Override - public int size() { - return PatriciaTrieBase.this.size(); - } - - @Override - public boolean contains(final Object o) { - return containsValue(o); - } - - @Override - public void clear() { - PatriciaTrieBase.this.clear(); - } - - @Override - public boolean remove(final Object o) { - for (final Iterator it = iterator(); it.hasNext(); ) { - final V value = it.next(); - if (compare(value, o)) { - it.remove(); - return true; - } - } - return false; - } - - /** - * An {@link Iterator} that returns Value Objects. - */ - private class ValueIterator extends TrieIterator { - public V next() { - return nextEntry().getValue(); - } - } - } - - /** - * An iterator for the entries. - */ - abstract class TrieIterator implements Iterator { - - /** For fast-fail. */ - protected int expectedModCount = PatriciaTrieBase.this.modCount; - - protected TrieEntry next; // the next node to return - protected TrieEntry current; // the current entry we're on - - /** - * Starts iteration from the root. - */ - protected TrieIterator() { - next = PatriciaTrieBase.this.nextEntry(null); - } - - /** - * Starts iteration at the given entry. - */ - protected TrieIterator(final TrieEntry firstEntry) { - next = firstEntry; - } - - /** - * Returns the next {@link TrieEntry}. - */ - protected TrieEntry nextEntry() { - if (expectedModCount != PatriciaTrieBase.this.modCount) { - throw new ConcurrentModificationException(); - } - - final TrieEntry e = next; - if (e == null) { - throw new NoSuchElementException(); - } - - next = findNext(e); - current = e; - return e; - } - - /** - * @see PatriciaTrie#nextEntry(TrieEntry) - */ - protected TrieEntry findNext(final TrieEntry prior) { - return PatriciaTrieBase.this.nextEntry(prior); - } - - public boolean hasNext() { - return next != null; - } - - public void remove() { - if (current == null) { - throw new IllegalStateException(); - } - - if (expectedModCount != PatriciaTrieBase.this.modCount) { - throw new ConcurrentModificationException(); - } - - final TrieEntry node = current; - current = null; - PatriciaTrieBase.this.removeEntry(node); - - expectedModCount = PatriciaTrieBase.this.modCount; - } - } -} diff --git a/src/main/java/org/apache/commons/collections4/trie/SynchronizedTrie.java b/src/main/java/org/apache/commons/collections4/trie/SynchronizedTrie.java index 8737dc7da..bfb43adc9 100644 --- a/src/main/java/org/apache/commons/collections4/trie/SynchronizedTrie.java +++ b/src/main/java/org/apache/commons/collections4/trie/SynchronizedTrie.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.Set; import java.util.SortedMap; +import org.apache.commons.collections4.OrderedMapIterator; import org.apache.commons.collections4.Trie; import org.apache.commons.collections4.collection.SynchronizedCollection; @@ -66,10 +67,6 @@ public class SynchronizedTrie implements Trie, Serializable { this.delegate = trie; } - public synchronized Entry traverse(final Cursor cursor) { - return delegate.traverse(cursor); - } - public synchronized Set> entrySet() { return Collections.synchronizedSet(delegate.entrySet()); } @@ -114,6 +111,10 @@ public class SynchronizedTrie implements Trie, Serializable { return delegate.remove(key); } + public synchronized int size() { + return delegate.size(); + } + public synchronized K lastKey() { return delegate.lastKey(); } @@ -138,31 +139,26 @@ public class SynchronizedTrie implements Trie, Serializable { return Collections.synchronizedSortedMap(delegate.headMap(toKey)); } - public synchronized SortedMap getPrefixedBy(final K key, final int offset, final int length) { - return Collections.synchronizedSortedMap(delegate.getPrefixedBy(key, offset, length)); + public synchronized SortedMap prefixMap(final K key) { + return Collections.synchronizedSortedMap(delegate.prefixMap(key)); } - public synchronized SortedMap getPrefixedBy(final K key, final int length) { - return Collections.synchronizedSortedMap(delegate.getPrefixedBy(key, length)); + //----------------------------------------------------------------------- + public synchronized OrderedMapIterator mapIterator() { + // TODO: make ordered map iterator synchronized too + final OrderedMapIterator it = delegate.mapIterator(); + return it; } - public synchronized SortedMap getPrefixedBy(final K key) { - return Collections.synchronizedSortedMap(delegate.getPrefixedBy(key)); + public synchronized K nextKey(K key) { + return delegate.nextKey(key); } - public synchronized SortedMap getPrefixedByBits(final K key, final int lengthInBits) { - return Collections.synchronizedSortedMap(delegate.getPrefixedByBits(key, lengthInBits)); - } - - public synchronized SortedMap getPrefixedByBits(final K key, - final int offsetInBits, final int lengthInBits) { - return Collections.synchronizedSortedMap(delegate.getPrefixedByBits(key, offsetInBits, lengthInBits)); - } - - public synchronized int size() { - return delegate.size(); + public synchronized K previousKey(K key) { + return delegate.previousKey(key); } + //----------------------------------------------------------------------- @Override public synchronized int hashCode() { return delegate.hashCode(); @@ -177,4 +173,5 @@ public class SynchronizedTrie implements Trie, Serializable { public synchronized String toString() { return delegate.toString(); } + } diff --git a/src/main/java/org/apache/commons/collections4/trie/UnmodifiableTrie.java b/src/main/java/org/apache/commons/collections4/trie/UnmodifiableTrie.java index 2db365dc9..2a04a3747 100644 --- a/src/main/java/org/apache/commons/collections4/trie/UnmodifiableTrie.java +++ b/src/main/java/org/apache/commons/collections4/trie/UnmodifiableTrie.java @@ -24,8 +24,10 @@ import java.util.Map; import java.util.Set; import java.util.SortedMap; +import org.apache.commons.collections4.OrderedMapIterator; import org.apache.commons.collections4.Trie; import org.apache.commons.collections4.Unmodifiable; +import org.apache.commons.collections4.iterators.UnmodifiableOrderedMapIterator; /** * An unmodifiable {@link Trie}. @@ -35,6 +37,7 @@ import org.apache.commons.collections4.Unmodifiable; */ public class UnmodifiableTrie implements Trie, Serializable, Unmodifiable { + /** Serialization version */ private static final long serialVersionUID = -7156426030315945159L; private final Trie delegate; @@ -66,25 +69,7 @@ public class UnmodifiableTrie implements Trie, Serializable, Unmodif this.delegate = trie; } - public Entry traverse(final Cursor cursor) { - final Cursor c = new Cursor() { - public Decision select(final Map.Entry entry) { - final Decision decision = cursor.select(entry); - switch (decision) { - case REMOVE: - case REMOVE_AND_EXIT: - throw new UnsupportedOperationException(); - default: - // other decisions are fine - break; - } - - return decision; - } - }; - - return delegate.traverse(c); - } + //----------------------------------------------------------------------- public Set> entrySet() { return Collections.unmodifiableSet(delegate.entrySet()); @@ -130,6 +115,10 @@ public class UnmodifiableTrie implements Trie, Serializable, Unmodif throw new UnsupportedOperationException(); } + public int size() { + return delegate.size(); + } + public K firstKey() { return delegate.firstKey(); } @@ -150,34 +139,29 @@ public class UnmodifiableTrie implements Trie, Serializable, Unmodif return Collections.unmodifiableSortedMap(delegate.tailMap(fromKey)); } - public SortedMap getPrefixedBy(final K key, final int offset, final int length) { - return Collections.unmodifiableSortedMap(delegate.getPrefixedBy(key, offset, length)); - } - - public SortedMap getPrefixedBy(final K key, final int length) { - return Collections.unmodifiableSortedMap(delegate.getPrefixedBy(key, length)); - } - - public SortedMap getPrefixedBy(final K key) { - return Collections.unmodifiableSortedMap(delegate.getPrefixedBy(key)); - } - - public SortedMap getPrefixedByBits(final K key, final int lengthInBits) { - return Collections.unmodifiableSortedMap(delegate.getPrefixedByBits(key, lengthInBits)); - } - - public SortedMap getPrefixedByBits(final K key, final int offsetInBits, final int lengthInBits) { - return Collections.unmodifiableSortedMap(delegate.getPrefixedByBits(key, offsetInBits, lengthInBits)); + public SortedMap prefixMap(final K key) { + return Collections.unmodifiableSortedMap(delegate.prefixMap(key)); } public Comparator comparator() { return delegate.comparator(); } - public int size() { - return delegate.size(); + //----------------------------------------------------------------------- + public OrderedMapIterator mapIterator() { + final OrderedMapIterator it = delegate.mapIterator(); + return UnmodifiableOrderedMapIterator.unmodifiableOrderedMapIterator(it); } + public K nextKey(K key) { + return delegate.nextKey(key); + } + + public K previousKey(K key) { + return delegate.previousKey(key); + } + + //----------------------------------------------------------------------- @Override public int hashCode() { return delegate.hashCode(); @@ -192,4 +176,5 @@ public class UnmodifiableTrie implements Trie, Serializable, Unmodif public String toString() { return delegate.toString(); } + } diff --git a/src/main/java/org/apache/commons/collections4/trie/analyzer/ByteArrayKeyAnalyzer.java b/src/main/java/org/apache/commons/collections4/trie/analyzer/ByteArrayKeyAnalyzer.java deleted file mode 100644 index f6df1df6f..000000000 --- a/src/main/java/org/apache/commons/collections4/trie/analyzer/ByteArrayKeyAnalyzer.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 - * - * 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 org.apache.commons.collections4.trie.analyzer; - -import org.apache.commons.collections4.trie.KeyAnalyzer; - -/** - * A {@link KeyAnalyzer} for byte[]s. - * - * @since 4.0 - * @version $Id$ - */ -public class ByteArrayKeyAnalyzer extends KeyAnalyzer { - - private static final long serialVersionUID = 7382825097492285877L; - - /** A singleton instance of {@link ByteArrayKeyAnalyzer}. */ - public static final ByteArrayKeyAnalyzer INSTANCE = new ByteArrayKeyAnalyzer(Integer.MAX_VALUE); - - /** The length of an {@link Byte} in bits. */ - public static final int LENGTH = Byte.SIZE; - - /** A bit mask where the first bit is 1 and the others are zero. */ - private static final int MSB = 0x80; - - /** A place holder for null. */ - private static final byte[] NULL = new byte[0]; - - /** The maximum length of a key in bits. */ - private final int maxLengthInBits; - - public ByteArrayKeyAnalyzer(final int maxLengthInBits) { - if (maxLengthInBits < 0) { - throw new IllegalArgumentException( - "maxLengthInBits=" + maxLengthInBits); - } - - this.maxLengthInBits = maxLengthInBits; - } - - /** - * Returns a bit mask where the given bit is set - */ - private static int mask(final int bit) { - return MSB >>> bit; - } - - /** - * Returns the maximum length of a key in bits - * @return the maximum key length in bits - */ - public int getMaxLengthInBits() { - return maxLengthInBits; - } - - public int bitsPerElement() { - return LENGTH; - } - - public int lengthInBits(final byte[] key) { - return key != null ? key.length * bitsPerElement() : 0; - } - - public boolean isBitSet(final byte[] key, final int bitIndex, final int lengthInBits) { - if (key == null) { - return false; - } - - final int prefix = maxLengthInBits - lengthInBits; - final int keyBitIndex = bitIndex - prefix; - - if (keyBitIndex >= lengthInBits || keyBitIndex < 0) { - return false; - } - - final int index = keyBitIndex / LENGTH; - final int bit = keyBitIndex % LENGTH; - return (key[index] & mask(bit)) != 0; - } - - public int bitIndex(final byte[] key, final int offsetInBits, final int lengthInBits, - byte[] other, final int otherOffsetInBits, final int otherLengthInBits) { - - if (other == null) { - other = NULL; - } - - boolean allNull = true; - final int length = Math.max(lengthInBits, otherLengthInBits); - final int prefix = maxLengthInBits - length; - - if (prefix < 0) { - return KeyAnalyzer.OUT_OF_BOUNDS_BIT_KEY; - } - - for (int i = 0; i < length; i++) { - final int index = prefix + offsetInBits + i; - final boolean value = isBitSet(key, index, lengthInBits); - - if (value) { - allNull = false; - } - - final int otherIndex = prefix + otherOffsetInBits + i; - final boolean otherValue = isBitSet(other, otherIndex, otherLengthInBits); - - if (value != otherValue) { - return index; - } - } - - if (allNull) { - return KeyAnalyzer.NULL_BIT_KEY; - } - - return KeyAnalyzer.EQUAL_BIT_KEY; - } - - public boolean isPrefix(final byte[] prefix, final int offsetInBits, final int lengthInBits, final byte[] key) { - - final int keyLength = lengthInBits(key); - if (lengthInBits > keyLength) { - return false; - } - - final int elements = lengthInBits - offsetInBits; - for (int i = 0; i < elements; i++) { - if (isBitSet(prefix, i+offsetInBits, lengthInBits) - != isBitSet(key, i, keyLength)) { - return false; - } - } - - return true; - } - - @Override - public int compare(final byte[] o1, final byte[] o2) { - if (o1 == null) { - return o2 == null ? 0 : -1; - } else if (o2 == null) { - return 1; - } - - if (o1.length != o2.length) { - return o1.length - o2.length; - } - - for (int i = 0; i < o1.length; i++) { - final int diff = (o1[i] & 0xFF) - (o2[i] & 0xFF); - if (diff != 0) { - return diff; - } - } - - return 0; - } -} diff --git a/src/main/java/org/apache/commons/collections4/trie/analyzer/ByteKeyAnalyzer.java b/src/main/java/org/apache/commons/collections4/trie/analyzer/ByteKeyAnalyzer.java deleted file mode 100644 index 1833f6258..000000000 --- a/src/main/java/org/apache/commons/collections4/trie/analyzer/ByteKeyAnalyzer.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 - * - * 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 org.apache.commons.collections4.trie.analyzer; - -import org.apache.commons.collections4.trie.KeyAnalyzer; - -/** - * A {@link KeyAnalyzer} for {@link Byte}s. - * - * @since 4.0 - * @version $Id$ - */ -public class ByteKeyAnalyzer extends KeyAnalyzer { - - private static final long serialVersionUID = 3395803342983289829L; - - /** A singleton instance of {@link ByteKeyAnalyzer}. */ - public static final ByteKeyAnalyzer INSTANCE = new ByteKeyAnalyzer(); - - /** The length of an {@link Byte} in bits. */ - public static final int LENGTH = Byte.SIZE; - - /** A bit mask where the first bit is 1 and the others are zero. */ - private static final int MSB = 0x80; - - /** Returns a bit mask where the given bit is set. */ - private static int mask(final int bit) { - return MSB >>> bit; - } - - public int bitsPerElement() { - return 1; - } - - public int lengthInBits(final Byte key) { - return LENGTH; - } - - public boolean isBitSet(final Byte key, final int bitIndex, final int lengthInBits) { - return (key.intValue() & mask(bitIndex)) != 0; - } - - public int bitIndex(final Byte key, final int offsetInBits, final int lengthInBits, - final Byte other, final int otherOffsetInBits, final int otherLengthInBits) { - - if (offsetInBits != 0 || otherOffsetInBits != 0) { - throw new IllegalArgumentException("offsetInBits=" + offsetInBits - + ", otherOffsetInBits=" + otherOffsetInBits); - } - - final byte keyValue = key.byteValue(); - if (keyValue == 0) { - return NULL_BIT_KEY; - } - - final byte otherValue = other != null ? other.byteValue() : 0; - - if (keyValue != otherValue) { - final int xorValue = keyValue ^ otherValue; - for (int i = 0; i < LENGTH; i++) { - if ((xorValue & mask(i)) != 0) { - return i; - } - } - } - - return KeyAnalyzer.EQUAL_BIT_KEY; - } - - public boolean isPrefix(final Byte prefix, final int offsetInBits, final int lengthInBits, final Byte key) { - - final int value1 = prefix.byteValue() << offsetInBits; - final int value2 = key.byteValue(); - - int mask = 0; - for (int i = 0; i < lengthInBits; i++) { - mask |= 0x1 << i; - } - - return (value1 & mask) == (value2 & mask); - } -} diff --git a/src/main/java/org/apache/commons/collections4/trie/analyzer/CharArrayKeyAnalyzer.java b/src/main/java/org/apache/commons/collections4/trie/analyzer/CharArrayKeyAnalyzer.java deleted file mode 100644 index 5a432e190..000000000 --- a/src/main/java/org/apache/commons/collections4/trie/analyzer/CharArrayKeyAnalyzer.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 - * - * 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 org.apache.commons.collections4.trie.analyzer; - -import org.apache.commons.collections4.trie.KeyAnalyzer; - -/** - * An {@link KeyAnalyzer} for {@code char[]}s. - * - * @since 4.0 - * @version $Id$ - */ -public class CharArrayKeyAnalyzer extends KeyAnalyzer { - - private static final long serialVersionUID = -8167897361549463457L; - - /** A singleton instance of {@link CharArrayKeyAnalyzer}. */ - public static final CharArrayKeyAnalyzer INSTANCE = new CharArrayKeyAnalyzer(); - - /** The number of bits per {@link Character}. */ - public static final int LENGTH = Character.SIZE; - - /** A bit mask where the first bit is 1 and the others are zero. */ - private static final int MSB = 0x8000; - - /** Returns a bit mask where the given bit is set. */ - private static int mask(final int bit) { - return MSB >>> bit; - } - - public int bitsPerElement() { - return LENGTH; - } - - public int lengthInBits(final char[] key) { - return key != null ? key.length * LENGTH : 0; - } - - public int bitIndex(final char[] key, final int offsetInBits, final int lengthInBits, - final char[] other, final int otherOffsetInBits, final int otherLengthInBits) { - boolean allNull = true; - - if (offsetInBits % LENGTH != 0 || otherOffsetInBits % LENGTH != 0 - || lengthInBits % LENGTH != 0 || otherLengthInBits % LENGTH != 0) { - throw new IllegalArgumentException( - "The offsets and lengths must be at Character boundaries"); - } - - - final int beginIndex1 = offsetInBits / LENGTH; - final int beginIndex2 = otherOffsetInBits / LENGTH; - - final int endIndex1 = beginIndex1 + lengthInBits / LENGTH; - final int endIndex2 = beginIndex2 + otherLengthInBits / LENGTH; - - final int length = Math.max(endIndex1, endIndex2); - - // Look at each character, and if they're different - // then figure out which bit makes the difference - // and return it. - char k = 0, f = 0; - for(int i = 0; i < length; i++) { - final int index1 = beginIndex1 + i; - final int index2 = beginIndex2 + i; - - if (index1 >= endIndex1) { - k = 0; - } else { - k = key[index1]; - } - - if (other == null || index2 >= endIndex2) { - f = 0; - } else { - f = other[index2]; - } - - if (k != f) { - final int x = k ^ f; - return i * LENGTH + Integer.numberOfLeadingZeros(x) - LENGTH; - } - - if (k != 0) { - allNull = false; - } - } - - // All bits are 0 - if (allNull) { - return KeyAnalyzer.NULL_BIT_KEY; - } - - // Both keys are equal - return KeyAnalyzer.EQUAL_BIT_KEY; - } - - public boolean isBitSet(final char[] key, final int bitIndex, final int lengthInBits) { - if (key == null || bitIndex >= lengthInBits) { - return false; - } - - final int index = bitIndex / LENGTH; - final int bit = bitIndex % LENGTH; - - return (key[index] & mask(bit)) != 0; - } - - public boolean isPrefix(final char[] prefix, final int offsetInBits, final int lengthInBits, final char[] key) { - if (offsetInBits % LENGTH != 0 || lengthInBits % LENGTH != 0) { - throw new IllegalArgumentException( - "Cannot determine prefix outside of Character boundaries"); - } - - final int off = offsetInBits / LENGTH; - final int len = lengthInBits / LENGTH; - for (int i = 0; i < len; i ++) { - if (prefix[i + off] != key[i]) { - return false; - } - } - return true; - } -} diff --git a/src/main/java/org/apache/commons/collections4/trie/analyzer/CharacterKeyAnalyzer.java b/src/main/java/org/apache/commons/collections4/trie/analyzer/CharacterKeyAnalyzer.java deleted file mode 100644 index dec38e5ef..000000000 --- a/src/main/java/org/apache/commons/collections4/trie/analyzer/CharacterKeyAnalyzer.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 - * - * 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 org.apache.commons.collections4.trie.analyzer; - -import org.apache.commons.collections4.trie.KeyAnalyzer; - -/** - * A {@link KeyAnalyzer} for {@link Character}s. - * - * @since 4.0 - * @version $Id$ - */ -public class CharacterKeyAnalyzer extends KeyAnalyzer { - - private static final long serialVersionUID = 3928565962744720753L; - - /** A singleton instance of the {@link CharacterKeyAnalyzer}. */ - public static final CharacterKeyAnalyzer INSTANCE - = new CharacterKeyAnalyzer(); - - /** The length of a {@link Character} in bits. */ - public static final int LENGTH = Character.SIZE; - - /** A bit mask where the first bit is 1 and the others are zero. */ - private static final int MSB = 0x8000; - - /** Returns a bit mask where the given bit is set. */ - private static int mask(final int bit) { - return MSB >>> bit; - } - - public int bitsPerElement() { - return 1; - } - - public int lengthInBits(final Character key) { - return LENGTH; - } - - public boolean isBitSet(final Character key, final int bitIndex, final int lengthInBits) { - return (key.charValue() & mask(bitIndex)) != 0; - } - - public int bitIndex(final Character key, final int offsetInBits, final int lengthInBits, - final Character other, final int otherOffsetInBits, final int otherLengthInBits) { - - if (offsetInBits != 0 || otherOffsetInBits != 0) { - throw new IllegalArgumentException("offsetInBits=" + offsetInBits - + ", otherOffsetInBits=" + otherOffsetInBits); - } - - final char keyValue = key.charValue(); - if (keyValue == Character.MIN_VALUE) { - return NULL_BIT_KEY; - } - - final char otherValue = other != null ? other.charValue() : Character.MIN_VALUE; - - if (keyValue != otherValue) { - final int xorValue = keyValue ^ otherValue; - for (int i = 0; i < LENGTH; i++) { - if ((xorValue & mask(i)) != 0) { - return i; - } - } - } - - return KeyAnalyzer.EQUAL_BIT_KEY; - } - - public boolean isPrefix(final Character prefix, final int offsetInBits, - final int lengthInBits, final Character key) { - - final int value1 = prefix.charValue() << offsetInBits; - final int value2 = key.charValue(); - - int mask = 0; - for(int i = 0; i < lengthInBits; i++) { - mask |= 0x1 << i; - } - - return (value1 & mask) == (value2 & mask); - } -} diff --git a/src/main/java/org/apache/commons/collections4/trie/analyzer/IntegerKeyAnalyzer.java b/src/main/java/org/apache/commons/collections4/trie/analyzer/IntegerKeyAnalyzer.java deleted file mode 100644 index b9efa3a89..000000000 --- a/src/main/java/org/apache/commons/collections4/trie/analyzer/IntegerKeyAnalyzer.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 - * - * 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 org.apache.commons.collections4.trie.analyzer; - -import org.apache.commons.collections4.trie.KeyAnalyzer; - -/** - * A {@link KeyAnalyzer} for {@link Integer}s. - * - * @since 4.0 - * @version $Id$ - */ -public class IntegerKeyAnalyzer extends KeyAnalyzer { - - private static final long serialVersionUID = 4928508653722068982L; - - /** A singleton instance of {@link IntegerKeyAnalyzer}. */ - public static final IntegerKeyAnalyzer INSTANCE = new IntegerKeyAnalyzer(); - - /** The length of an {@link Integer} in bits. */ - public static final int LENGTH = Integer.SIZE; - - /** A bit mask where the first bit is 1 and the others are zero. */ - private static final int MSB = 0x80000000; - - /** Returns a bit mask where the given bit is set. */ - private static int mask(final int bit) { - return MSB >>> bit; - } - - public int bitsPerElement() { - return 1; - } - - public int lengthInBits(final Integer key) { - return LENGTH; - } - - public boolean isBitSet(final Integer key, final int bitIndex, final int lengthInBits) { - return (key.intValue() & mask(bitIndex)) != 0; - } - - public int bitIndex(final Integer key, final int offsetInBits, final int lengthInBits, - final Integer other, final int otherOffsetInBits, final int otherLengthInBits) { - - if (offsetInBits != 0 || otherOffsetInBits != 0) { - throw new IllegalArgumentException("offsetInBits=" + offsetInBits - + ", otherOffsetInBits=" + otherOffsetInBits); - } - - final int keyValue = key.intValue(); - if (keyValue == 0) { - return NULL_BIT_KEY; - } - - final int otherValue = other != null ? other.intValue() : 0; - - if (keyValue != otherValue) { - final int xorValue = keyValue ^ otherValue; - for (int i = 0; i < LENGTH; i++) { - if ((xorValue & mask(i)) != 0) { - return i; - } - } - } - - return KeyAnalyzer.EQUAL_BIT_KEY; - } - - public boolean isPrefix(final Integer prefix, final int offsetInBits, - final int lengthInBits, final Integer key) { - - final int value1 = prefix.intValue() << offsetInBits; - final int value2 = key.intValue(); - - int mask = 0; - for (int i = 0; i < lengthInBits; i++) { - mask |= 0x1 << i; - } - - return (value1 & mask) == (value2 & mask); - } -} diff --git a/src/main/java/org/apache/commons/collections4/trie/analyzer/LongKeyAnalyzer.java b/src/main/java/org/apache/commons/collections4/trie/analyzer/LongKeyAnalyzer.java deleted file mode 100644 index 82f6b0f5c..000000000 --- a/src/main/java/org/apache/commons/collections4/trie/analyzer/LongKeyAnalyzer.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 - * - * 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 org.apache.commons.collections4.trie.analyzer; - -import org.apache.commons.collections4.trie.KeyAnalyzer; - -/** - * A {@link KeyAnalyzer} for {@link Long}s. - * - * @since 4.0 - * @version $Id$ - */ -public class LongKeyAnalyzer extends KeyAnalyzer { - - private static final long serialVersionUID = -4119639247588227409L; - - /** A singleton instance of {@link LongKeyAnalyzer}. */ - public static final LongKeyAnalyzer INSTANCE = new LongKeyAnalyzer(); - - /** The length of an {@link Long} in bits. */ - public static final int LENGTH = Long.SIZE; - - /** A bit mask where the first bit is 1 and the others are zero. */ - private static final long MSB = 0x8000000000000000L; - - /** Returns a bit mask where the given bit is set. */ - private static long mask(final int bit) { - return MSB >>> bit; - } - - public int bitsPerElement() { - return 1; - } - - public int lengthInBits(final Long key) { - return LENGTH; - } - - public boolean isBitSet(final Long key, final int bitIndex, final int lengthInBits) { - return (key.longValue() & mask(bitIndex)) != 0; - } - - public int bitIndex(final Long key, final int offsetInBits, final int lengthInBits, - final Long other, final int otherOffsetInBits, final int otherLengthInBits) { - - if (offsetInBits != 0 || otherOffsetInBits != 0) { - throw new IllegalArgumentException("offsetInBits=" + offsetInBits - + ", otherOffsetInBits=" + otherOffsetInBits); - } - - final long keyValue = key.longValue(); - if (keyValue == 0L) { - return NULL_BIT_KEY; - } - - final long otherValue = other != null ? other.longValue() : 0L; - - if (keyValue != otherValue) { - final long xorValue = keyValue ^ otherValue; - for (int i = 0; i < LENGTH; i++) { - if ((xorValue & mask(i)) != 0L) { - return i; - } - } - } - - return KeyAnalyzer.EQUAL_BIT_KEY; - } - - public boolean isPrefix(final Long prefix, final int offsetInBits, - final int lengthInBits, final Long key) { - - final long value1 = prefix.longValue() << offsetInBits; - final long value2 = key.longValue(); - - long mask = 0L; - for (int i = 0; i < lengthInBits; i++) { - mask |= 0x1L << i; - } - - return (value1 & mask) == (value2 & mask); - } -} diff --git a/src/main/java/org/apache/commons/collections4/trie/analyzer/ShortKeyAnalyzer.java b/src/main/java/org/apache/commons/collections4/trie/analyzer/ShortKeyAnalyzer.java deleted file mode 100644 index 5a97f1ff0..000000000 --- a/src/main/java/org/apache/commons/collections4/trie/analyzer/ShortKeyAnalyzer.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 - * - * 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 org.apache.commons.collections4.trie.analyzer; - -import org.apache.commons.collections4.trie.KeyAnalyzer; - -/** - * A {@link KeyAnalyzer} for {@link Short}s. - * - * @since 4.0 - * @version $Id$ - */ -public class ShortKeyAnalyzer extends KeyAnalyzer { - - private static final long serialVersionUID = -8631376733513512017L; - - /** A singleton instance of {@link ShortKeyAnalyzer}. */ - public static final ShortKeyAnalyzer INSTANCE = new ShortKeyAnalyzer(); - - /** The length of an {@link Short} in bits. */ - public static final int LENGTH = Short.SIZE; - - /** A bit mask where the first bit is 1 and the others are zero. */ - private static final int MSB = 0x8000; - - /** Returns a bit mask where the given bit is set. */ - private static int mask(final int bit) { - return MSB >>> bit; - } - - public int bitsPerElement() { - return 1; - } - - public int lengthInBits(final Short key) { - return LENGTH; - } - - public boolean isBitSet(final Short key, final int bitIndex, final int lengthInBits) { - return (key.intValue() & mask(bitIndex)) != 0; - } - - public int bitIndex(final Short key, final int offsetInBits, final int lengthInBits, - final Short other, final int otherOffsetInBits, final int otherLengthInBits) { - - if (offsetInBits != 0 || otherOffsetInBits != 0) { - throw new IllegalArgumentException("offsetInBits=" + offsetInBits - + ", otherOffsetInBits=" + otherOffsetInBits); - } - - final int keyValue = key.shortValue(); - if (keyValue == 0) { - return NULL_BIT_KEY; - } - - final int otherValue = other != null ? other.shortValue() : 0; - - if (keyValue != otherValue) { - final int xorValue = keyValue ^ otherValue; - for (int i = 0; i < LENGTH; i++) { - if ((xorValue & mask(i)) != 0) { - return i; - } - } - } - - return KeyAnalyzer.EQUAL_BIT_KEY; - } - - public boolean isPrefix(final Short prefix, final int offsetInBits, - final int lengthInBits, final Short key) { - - final int value1 = prefix.shortValue() << offsetInBits; - final int value2 = key.shortValue(); - - int mask = 0; - for (int i = 0; i < lengthInBits; i++) { - mask |= 0x1 << i; - } - - return (value1 & mask) == (value2 & mask); - } - - @Override - public int compare(final Short o1, final Short o2) { - return o1.compareTo(o2); - } - -} diff --git a/src/test/java/org/apache/commons/collections4/trie/PatriciaTrie2Test.java b/src/test/java/org/apache/commons/collections4/trie/PatriciaTrie2Test.java new file mode 100644 index 000000000..cd136e544 --- /dev/null +++ b/src/test/java/org/apache/commons/collections4/trie/PatriciaTrie2Test.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * 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 org.apache.commons.collections4.trie; + +import junit.framework.Test; + +import org.apache.commons.collections4.BulkTest; +import org.apache.commons.collections4.OrderedMap; +import org.apache.commons.collections4.map.AbstractOrderedMapTest; + +/** + * JUnit test of the OrderedMap interface of a PatriciaTrie. + * + * @since 4.0 + * @version $Id$ + */ +public class PatriciaTrie2Test extends AbstractOrderedMapTest { + + public PatriciaTrie2Test(final String testName) { + super(testName); + } + + public static Test suite() { + return BulkTest.makeSuite(PatriciaTrie2Test.class); + } + + @Override + public OrderedMap makeObject() { + return new PatriciaTrie(); + } + + @Override + public boolean isAllowNullKey() { + return false; + } + + //----------------------------------------------------------------------- + + @Override + public String getCompatibilityVersion() { + return "4"; + } + +} diff --git a/src/test/java/org/apache/commons/collections4/trie/PatriciaTrieTest.java b/src/test/java/org/apache/commons/collections4/trie/PatriciaTrieTest.java index 96c166256..b36737a10 100755 --- a/src/test/java/org/apache/commons/collections4/trie/PatriciaTrieTest.java +++ b/src/test/java/org/apache/commons/collections4/trie/PatriciaTrieTest.java @@ -16,669 +16,50 @@ */ package org.apache.commons.collections4.trie; -import java.io.BufferedReader; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.Iterator; -import java.util.List; import java.util.Map; import java.util.NoSuchElementException; -import java.util.Random; import java.util.SortedMap; -import java.util.StringTokenizer; -import java.util.TreeMap; -import java.util.Map.Entry; -import org.apache.commons.collections4.Trie.Cursor; -import org.apache.commons.collections4.trie.analyzer.CharacterKeyAnalyzer; -import org.apache.commons.collections4.trie.analyzer.IntegerKeyAnalyzer; -import org.apache.commons.collections4.trie.analyzer.StringKeyAnalyzer; +import junit.framework.Test; + +import org.apache.commons.collections4.BulkTest; +import org.apache.commons.collections4.map.AbstractSortedMapTest; +import org.apache.commons.collections4.trie.PatriciaTrie; import org.junit.Assert; -import org.junit.Test; /** - * JUnit tests. - * - * FIXME: add serialization support - * TODO: integrate into test framework, utilize existing map tests + * JUnit tests for the PatriciaTrie. * * @since 4.0 * @version $Id$ */ -public class PatriciaTrieTest { +public class PatriciaTrieTest extends AbstractSortedMapTest { - @Test - @SuppressWarnings("boxing") // OK in test code - public void testSimple() { - final PatriciaTrie intTrie = new PatriciaTrie(new IntegerKeyAnalyzer()); - Assert.assertTrue(intTrie.isEmpty()); - Assert.assertEquals(0, intTrie.size()); - - intTrie.put(1, "One"); - Assert.assertFalse(intTrie.isEmpty()); - Assert.assertEquals(1, intTrie.size()); - - Assert.assertEquals("One", intTrie.remove(1)); - Assert.assertNull(intTrie.remove(1)); - Assert.assertTrue(intTrie.isEmpty()); - Assert.assertEquals(0, intTrie.size()); - - intTrie.put(1, "One"); - Assert.assertEquals("One", intTrie.get(1)); - Assert.assertEquals("One", intTrie.put(1, "NotOne")); - Assert.assertEquals(1, intTrie.size()); - Assert.assertEquals("NotOne", intTrie.get(1)); - Assert.assertEquals("NotOne", intTrie.remove(1)); - Assert.assertNull(intTrie.put(1, "One")); + public PatriciaTrieTest(final String testName) { + super(testName); } - @Test - @SuppressWarnings("boxing") // OK in test code - public void testCeilingEntry() { - final PatriciaTrie charTrie - = new PatriciaTrie(new CharacterKeyAnalyzer()); - charTrie.put('c', "c"); - charTrie.put('p', "p"); - charTrie.put('l', "l"); - charTrie.put('t', "t"); - charTrie.put('k', "k"); - charTrie.put('a', "a"); - charTrie.put('y', "y"); - charTrie.put('r', "r"); - charTrie.put('u', "u"); - charTrie.put('o', "o"); - charTrie.put('w', "w"); - charTrie.put('i', "i"); - charTrie.put('e', "e"); - charTrie.put('x', "x"); - charTrie.put('q', "q"); - charTrie.put('b', "b"); - charTrie.put('j', "j"); - charTrie.put('s', "s"); - charTrie.put('n', "n"); - charTrie.put('v', "v"); - charTrie.put('g', "g"); - charTrie.put('h', "h"); - charTrie.put('m', "m"); - charTrie.put('z', "z"); - charTrie.put('f', "f"); - charTrie.put('d', "d"); - - final Object[] results = new Object[] { - 'a', "a", 'b', "b", 'c', "c", 'd', "d", 'e', "e", - 'f', "f", 'g', "g", 'h', "h", 'i', "i", 'j', "j", - 'k', "k", 'l', "l", 'm', "m", 'n', "n", 'o', "o", - 'p', "p", 'q', "q", 'r', "r", 's', "s", 't', "t", - 'u', "u", 'v', "v", 'w', "w", 'x', "x", 'y', "y", - 'z', "z" - }; - - for(int i = 0; i < results.length; i++) { - final Map.Entry found = charTrie.ceilingEntry((Character)results[i]); - Assert.assertNotNull(found); - Assert.assertEquals(results[i], found.getKey()); - Assert.assertEquals(results[++i], found.getValue()); - } - - // Remove some & try again... - charTrie.remove('a'); - charTrie.remove('z'); - charTrie.remove('q'); - charTrie.remove('l'); - charTrie.remove('p'); - charTrie.remove('m'); - charTrie.remove('u'); - - Map.Entry found = charTrie.ceilingEntry('u'); - Assert.assertNotNull(found); - Assert.assertEquals((Character)'v', found.getKey()); - - found = charTrie.ceilingEntry('a'); - Assert.assertNotNull(found); - Assert.assertEquals((Character)'b', found.getKey()); - - found = charTrie.ceilingEntry('z'); - Assert.assertNull(found); - - found = charTrie.ceilingEntry('q'); - Assert.assertNotNull(found); - Assert.assertEquals((Character)'r', found.getKey()); - - found = charTrie.ceilingEntry('l'); - Assert.assertNotNull(found); - Assert.assertEquals((Character)'n', found.getKey()); - - found = charTrie.ceilingEntry('p'); - Assert.assertNotNull(found); - Assert.assertEquals((Character)'r', found.getKey()); - - found = charTrie.ceilingEntry('m'); - Assert.assertNotNull(found); - Assert.assertEquals((Character)'n', found.getKey()); - - found = charTrie.ceilingEntry('\0'); - Assert.assertNotNull(found); - Assert.assertEquals((Character)'b', found.getKey()); - - charTrie.put('\0', ""); - found = charTrie.ceilingEntry('\0'); - Assert.assertNotNull(found); - Assert.assertEquals((Character)'\0', found.getKey()); + public static Test suite() { + return BulkTest.makeSuite(PatriciaTrieTest.class); } - @Test - @SuppressWarnings("boxing") // OK in test code - public void testLowerEntry() { - final PatriciaTrie charTrie = new PatriciaTrie(new CharacterKeyAnalyzer()); - charTrie.put('c', "c"); - charTrie.put('p', "p"); - charTrie.put('l', "l"); - charTrie.put('t', "t"); - charTrie.put('k', "k"); - charTrie.put('a', "a"); - charTrie.put('y', "y"); - charTrie.put('r', "r"); - charTrie.put('u', "u"); - charTrie.put('o', "o"); - charTrie.put('w', "w"); - charTrie.put('i', "i"); - charTrie.put('e', "e"); - charTrie.put('x', "x"); - charTrie.put('q', "q"); - charTrie.put('b', "b"); - charTrie.put('j', "j"); - charTrie.put('s', "s"); - charTrie.put('n', "n"); - charTrie.put('v', "v"); - charTrie.put('g', "g"); - charTrie.put('h', "h"); - charTrie.put('m', "m"); - charTrie.put('z', "z"); - charTrie.put('f', "f"); - charTrie.put('d', "d"); - - final Object[] results = new Object[] { - 'a', "a", 'b', "b", 'c', "c", 'd', "d", 'e', "e", - 'f', "f", 'g', "g", 'h', "h", 'i', "i", 'j', "j", - 'k', "k", 'l', "l", 'm', "m", 'n', "n", 'o', "o", - 'p', "p", 'q', "q", 'r', "r", 's', "s", 't', "t", - 'u', "u", 'v', "v", 'w', "w", 'x', "x", 'y', "y", - 'z', "z" - }; - - for(int i = 0; i < results.length; i+=2) { - //System.out.println("Looking for: " + results[i]); - final Map.Entry found = charTrie.lowerEntry((Character)results[i]); - if(i == 0) { - Assert.assertNull(found); - } else { - Assert.assertNotNull(found); - Assert.assertEquals(results[i-2], found.getKey()); - Assert.assertEquals(results[i-1], found.getValue()); - } - } - - Map.Entry found = charTrie.lowerEntry((char)('z' + 1)); - Assert.assertNotNull(found); - Assert.assertEquals((Character)'z', found.getKey()); - - // Remove some & try again... - charTrie.remove('a'); - charTrie.remove('z'); - charTrie.remove('q'); - charTrie.remove('l'); - charTrie.remove('p'); - charTrie.remove('m'); - charTrie.remove('u'); - - found = charTrie.lowerEntry('u'); - Assert.assertNotNull(found); - Assert.assertEquals((Character)'t', found.getKey()); - - found = charTrie.lowerEntry('v'); - Assert.assertNotNull(found); - Assert.assertEquals((Character)'t', found.getKey()); - - found = charTrie.lowerEntry('a'); - Assert.assertNull(found); - - found = charTrie.lowerEntry('z'); - Assert.assertNotNull(found); - Assert.assertEquals((Character)'y', found.getKey()); - - found = charTrie.lowerEntry((char)('z'+1)); - Assert.assertNotNull(found); - Assert.assertEquals((Character)'y', found.getKey()); - - found = charTrie.lowerEntry('q'); - Assert.assertNotNull(found); - Assert.assertEquals((Character)'o', found.getKey()); - - found = charTrie.lowerEntry('r'); - Assert.assertNotNull(found); - Assert.assertEquals((Character)'o', found.getKey()); - - found = charTrie.lowerEntry('p'); - Assert.assertNotNull(found); - Assert.assertEquals((Character)'o', found.getKey()); - - found = charTrie.lowerEntry('l'); - Assert.assertNotNull(found); - Assert.assertEquals((Character)'k', found.getKey()); - - found = charTrie.lowerEntry('m'); - Assert.assertNotNull(found); - Assert.assertEquals((Character)'k', found.getKey()); - - found = charTrie.lowerEntry('\0'); - Assert.assertNull(found); - - charTrie.put('\0', ""); - found = charTrie.lowerEntry('\0'); - Assert.assertNull(found); + @Override + public SortedMap makeObject() { + return new PatriciaTrie(); } - @Test - @SuppressWarnings("boxing") // OK in test code - public void testIteration() { - final PatriciaTrie intTrie = new PatriciaTrie(new IntegerKeyAnalyzer()); - intTrie.put(1, "One"); - intTrie.put(5, "Five"); - intTrie.put(4, "Four"); - intTrie.put(2, "Two"); - intTrie.put(3, "Three"); - intTrie.put(15, "Fifteen"); - intTrie.put(13, "Thirteen"); - intTrie.put(14, "Fourteen"); - intTrie.put(16, "Sixteen"); - - TestCursor cursor = new TestCursor( - 1, "One", 2, "Two", 3, "Three", 4, "Four", 5, "Five", 13, "Thirteen", - 14, "Fourteen", 15, "Fifteen", 16, "Sixteen"); - - cursor.starting(); - intTrie.traverse(cursor); - cursor.finished(); - - cursor.starting(); - for (final Map.Entry entry : intTrie.entrySet()) { - cursor.select(entry); - } - cursor.finished(); - - cursor.starting(); - for (final Integer integer : intTrie.keySet()) { - cursor.checkKey(integer); - } - cursor.finished(); - - cursor.starting(); - for (final String string : intTrie.values()) { - cursor.checkValue(string); - } - cursor.finished(); - - final PatriciaTrie charTrie = new PatriciaTrie(new CharacterKeyAnalyzer()); - charTrie.put('c', "c"); - charTrie.put('p', "p"); - charTrie.put('l', "l"); - charTrie.put('t', "t"); - charTrie.put('k', "k"); - charTrie.put('a', "a"); - charTrie.put('y', "y"); - charTrie.put('r', "r"); - charTrie.put('u', "u"); - charTrie.put('o', "o"); - charTrie.put('w', "w"); - charTrie.put('i', "i"); - charTrie.put('e', "e"); - charTrie.put('x', "x"); - charTrie.put('q', "q"); - charTrie.put('b', "b"); - charTrie.put('j', "j"); - charTrie.put('s', "s"); - charTrie.put('n', "n"); - charTrie.put('v', "v"); - charTrie.put('g', "g"); - charTrie.put('h', "h"); - charTrie.put('m', "m"); - charTrie.put('z', "z"); - charTrie.put('f', "f"); - charTrie.put('d', "d"); - cursor = new TestCursor('a', "a", 'b', "b", 'c', "c", 'd', "d", 'e', "e", - 'f', "f", 'g', "g", 'h', "h", 'i', "i", 'j', "j", - 'k', "k", 'l', "l", 'm', "m", 'n', "n", 'o', "o", - 'p', "p", 'q', "q", 'r', "r", 's', "s", 't', "t", - 'u', "u", 'v', "v", 'w', "w", 'x', "x", 'y', "y", - 'z', "z"); - - cursor.starting(); - charTrie.traverse(cursor); - cursor.finished(); - - cursor.starting(); - for (final Map.Entry entry : charTrie.entrySet()) { - cursor.select(entry); - } - cursor.finished(); - - cursor.starting(); - for (final Character character : charTrie.keySet()) { - cursor.checkKey(character); - } - cursor.finished(); - - cursor.starting(); - for (final String string : charTrie.values()) { - cursor.checkValue(string); - } - cursor.finished(); + @Override + public boolean isAllowNullKey() { + return false; } - @Test - @SuppressWarnings("boxing") // OK in test code - public void testSelect() { - final PatriciaTrie charTrie = new PatriciaTrie(new CharacterKeyAnalyzer()); - charTrie.put('c', "c"); - charTrie.put('p', "p"); - charTrie.put('l', "l"); - charTrie.put('t', "t"); - charTrie.put('k', "k"); - charTrie.put('a', "a"); - charTrie.put('y', "y"); - charTrie.put('r', "r"); - charTrie.put('u', "u"); - charTrie.put('o', "o"); - charTrie.put('w', "w"); - charTrie.put('i', "i"); - charTrie.put('e', "e"); - charTrie.put('x', "x"); - charTrie.put('q', "q"); - charTrie.put('b', "b"); - charTrie.put('j', "j"); - charTrie.put('s', "s"); - charTrie.put('n', "n"); - charTrie.put('v', "v"); - charTrie.put('g', "g"); - charTrie.put('h', "h"); - charTrie.put('m', "m"); - charTrie.put('z', "z"); - charTrie.put('f', "f"); - charTrie.put('d', "d"); - final TestCursor cursor = new TestCursor( - 'd', "d", 'e', "e", 'f', "f", 'g', "g", - 'a', "a", 'b', "b", 'c', "c", - 'l', "l", 'm', "m", 'n', "n", 'o', "o", - 'h', "h", 'i', "i", 'j', "j", 'k', "k", - 't', "t", 'u', "u", 'v', "v", 'w', "w", - 'p', "p", 'q', "q", 'r', "r", 's', "s", - 'x', "x", 'y', "y", 'z', "z"); + //----------------------------------------------------------------------- - Assert.assertEquals(26, charTrie.size()); - - cursor.starting(); - charTrie.select('d', cursor); - cursor.finished(); - } - - @Test - @SuppressWarnings("boxing") // OK in test code - public void testTraverseCursorRemove() { - final PatriciaTrie charTrie = new PatriciaTrie(new CharacterKeyAnalyzer()); - charTrie.put('c', "c"); - charTrie.put('p', "p"); - charTrie.put('l', "l"); - charTrie.put('t', "t"); - charTrie.put('k', "k"); - charTrie.put('a', "a"); - charTrie.put('y', "y"); - charTrie.put('r', "r"); - charTrie.put('u', "u"); - charTrie.put('o', "o"); - charTrie.put('w', "w"); - charTrie.put('i', "i"); - charTrie.put('e', "e"); - charTrie.put('x', "x"); - charTrie.put('q', "q"); - charTrie.put('b', "b"); - charTrie.put('j', "j"); - charTrie.put('s', "s"); - charTrie.put('n', "n"); - charTrie.put('v', "v"); - charTrie.put('g', "g"); - charTrie.put('h', "h"); - charTrie.put('m', "m"); - charTrie.put('z', "z"); - charTrie.put('f', "f"); - charTrie.put('d', "d"); - final TestCursor cursor = new TestCursor('a', "a", 'b', "b", 'c', "c", 'd', "d", 'e', "e", - 'f', "f", 'g', "g", 'h', "h", 'i', "i", 'j', "j", - 'k', "k", 'l', "l", 'm', "m", 'n', "n", 'o', "o", - 'p', "p", 'q', "q", 'r', "r", 's', "s", 't', "t", - 'u', "u", 'v', "v", 'w', "w", 'x', "x", 'y', "y", - 'z', "z"); - - cursor.starting(); - charTrie.traverse(cursor); - cursor.finished(); - - // Test removing both an internal & external node. - // 'm' is an example External node in this Trie, and 'p' is an internal. - - Assert.assertEquals(26, charTrie.size()); - - final Object[] toRemove = new Object[] { 'g', 'd', 'e', 'm', 'p', 'q', 'r', 's' }; - cursor.addToRemove(toRemove); - - cursor.starting(); - charTrie.traverse(cursor); - cursor.finished(); - - Assert.assertEquals(26 - toRemove.length, charTrie.size()); - - cursor.starting(); - charTrie.traverse(cursor); - cursor.finished(); - - cursor.starting(); - for (final Entry entry : charTrie.entrySet()) { - cursor.select(entry); - if (Arrays.asList(toRemove).contains(entry.getKey())) { - Assert.fail("got an: " + entry); - } - } - cursor.finished(); - } - - @Test - @SuppressWarnings("boxing") // OK in test code - public void testIteratorRemove() { - final PatriciaTrie charTrie = new PatriciaTrie(new CharacterKeyAnalyzer()); - charTrie.put('c', "c"); - charTrie.put('p', "p"); - charTrie.put('l', "l"); - charTrie.put('t', "t"); - charTrie.put('k', "k"); - charTrie.put('a', "a"); - charTrie.put('y', "y"); - charTrie.put('r', "r"); - charTrie.put('u', "u"); - charTrie.put('o', "o"); - charTrie.put('w', "w"); - charTrie.put('i', "i"); - charTrie.put('e', "e"); - charTrie.put('x', "x"); - charTrie.put('q', "q"); - charTrie.put('b', "b"); - charTrie.put('j', "j"); - charTrie.put('s', "s"); - charTrie.put('n', "n"); - charTrie.put('v', "v"); - charTrie.put('g', "g"); - charTrie.put('h', "h"); - charTrie.put('m', "m"); - charTrie.put('z', "z"); - charTrie.put('f', "f"); - charTrie.put('d', "d"); - final TestCursor cursor = new TestCursor('a', "a", 'b', "b", 'c', "c", 'd', "d", 'e', "e", - 'f', "f", 'g', "g", 'h', "h", 'i', "i", 'j', "j", - 'k', "k", 'l', "l", 'm', "m", 'n', "n", 'o', "o", - 'p', "p", 'q', "q", 'r', "r", 's', "s", 't', "t", - 'u', "u", 'v', "v", 'w', "w", 'x', "x", 'y', "y", - 'z', "z"); - - // Test removing both an internal & external node. - // 'm' is an example External node in this Trie, and 'p' is an internal. - - Assert.assertEquals(26, charTrie.size()); - - final Object[] toRemove = new Object[] { 'e', 'm', 'p', 'q', 'r', 's' }; - - cursor.starting(); - for(final Iterator> i = charTrie.entrySet().iterator(); i.hasNext(); ) { - final Map.Entry entry = i.next(); - cursor.select(entry); - if(Arrays.asList(toRemove).contains(entry.getKey())) { - i.remove(); - } - } - cursor.finished(); - - Assert.assertEquals(26 - toRemove.length, charTrie.size()); - - cursor.remove(toRemove); - - cursor.starting(); - for (final Entry entry : charTrie.entrySet()) { - cursor.select(entry); - if (Arrays.asList(toRemove).contains(entry.getKey())) { - Assert.fail("got an: " + entry); - } - } - cursor.finished(); - } - - @Test - public void testHamlet() throws Exception { - // Make sure that Hamlet is read & stored in the same order as a SortedSet. - final List original = new ArrayList(); - final List control = new ArrayList(); - final SortedMap sortedControl = new TreeMap(); - final PatriciaTrie trie = new PatriciaTrie(new StringKeyAnalyzer()); - - final InputStream in = getClass().getResourceAsStream("hamlet.txt"); - final BufferedReader reader = new BufferedReader(new InputStreamReader(in)); - - String read = null; - while( (read = reader.readLine()) != null) { - final StringTokenizer st = new StringTokenizer(read); - while(st.hasMoreTokens()) { - final String token = st.nextToken(); - original.add(token); - sortedControl.put(token, token); - trie.put(token, token); - } - } - control.addAll(sortedControl.values()); - - Assert.assertEquals(control.size(), sortedControl.size()); - Assert.assertEquals(sortedControl.size(), trie.size()); - Iterator iter = trie.values().iterator(); - for (final String aControl : control) { - Assert.assertEquals(aControl, iter.next()); - } - - final Random rnd = new Random(); - int item = 0; - iter = trie.values().iterator(); - int removed = 0; - for(; item < control.size(); item++) { - Assert.assertEquals(control.get(item), iter.next()); - if(rnd.nextBoolean()) { - iter.remove(); - removed++; - } - } - - Assert.assertEquals(control.size(), item); - Assert.assertTrue(removed > 0); - Assert.assertEquals(control.size(), trie.size() + removed); - - // reset hamlet - trie.clear(); - for (final String anOriginal : original) { - trie.put(anOriginal, anOriginal); - } - - assertEqualArrays(sortedControl.values().toArray(), trie.values().toArray()); - assertEqualArrays(sortedControl.keySet().toArray(), trie.keySet().toArray()); - assertEqualArrays(sortedControl.entrySet().toArray(), trie.entrySet().toArray()); - - Assert.assertEquals(sortedControl.firstKey(), trie.firstKey()); - Assert.assertEquals(sortedControl.lastKey(), trie.lastKey()); - - SortedMap sub = trie.headMap(control.get(523)); - Assert.assertEquals(523, sub.size()); - for(int i = 0; i < control.size(); i++) { - if(i < 523) { - Assert.assertTrue(sub.containsKey(control.get(i))); - } else { - Assert.assertFalse(sub.containsKey(control.get(i))); - } - } - // Too slow to check values on all, so just do a few. - Assert.assertTrue(sub.containsValue(control.get(522))); - Assert.assertFalse(sub.containsValue(control.get(523))); - Assert.assertFalse(sub.containsValue(control.get(524))); - - try { - sub.headMap(control.get(524)); - Assert.fail("should have thrown IAE"); - } catch(final IllegalArgumentException expected) {} - - Assert.assertEquals(sub.lastKey(), control.get(522)); - Assert.assertEquals(sub.firstKey(), control.get(0)); - - sub = sub.tailMap(control.get(234)); - Assert.assertEquals(289, sub.size()); - Assert.assertEquals(control.get(234), sub.firstKey()); - Assert.assertEquals(control.get(522), sub.lastKey()); - for(int i = 0; i < control.size(); i++) { - if(i < 523 && i > 233) { - Assert.assertTrue(sub.containsKey(control.get(i))); - } else { - Assert.assertFalse(sub.containsKey(control.get(i))); - } - } - - try { - sub.tailMap(control.get(232)); - Assert.fail("should have thrown IAE"); - } catch(final IllegalArgumentException expected) {} - - sub = sub.subMap(control.get(300), control.get(400)); - Assert.assertEquals(100, sub.size()); - Assert.assertEquals(control.get(300), sub.firstKey()); - Assert.assertEquals(control.get(399), sub.lastKey()); - - for(int i = 0; i < control.size(); i++) { - if(i < 400 && i > 299) { - Assert.assertTrue(sub.containsKey(control.get(i))); - } else { - Assert.assertFalse(sub.containsKey(control.get(i))); - } - } - } - - @Test - public void testPrefixedBy() { - final PatriciaTrie trie - = new PatriciaTrie(new StringKeyAnalyzer()); + public void testPrefixMap() { + final PatriciaTrie trie = new PatriciaTrie(); final String[] keys = new String[]{ "", @@ -697,7 +78,7 @@ public class PatriciaTrieTest { Iterator> entryIterator; Map.Entry entry; - map = trie.getPrefixedBy("Al"); + map = trie.prefixMap("Al"); Assert.assertEquals(8, map.size()); Assert.assertEquals("Alabama", map.firstKey()); Assert.assertEquals("Alliese", map.lastKey()); @@ -717,7 +98,7 @@ public class PatriciaTrieTest { Assert.assertEquals("Alliese", iterator.next()); Assert.assertFalse(iterator.hasNext()); - map = trie.getPrefixedBy("Albert"); + map = trie.prefixMap("Albert"); iterator = map.keySet().iterator(); Assert.assertEquals("Albert", iterator.next()); Assert.assertEquals("Alberto", iterator.next()); @@ -741,7 +122,7 @@ public class PatriciaTrieTest { Assert.assertFalse(iterator.hasNext()); Assert.assertEquals("Albertz", map.remove("Albertz")); - map = trie.getPrefixedBy("Alberto"); + map = trie.prefixMap("Alberto"); Assert.assertEquals(2, map.size()); Assert.assertEquals("Alberto", map.firstKey()); Assert.assertEquals("Albertoo", map.lastKey()); @@ -783,7 +164,7 @@ public class PatriciaTrieTest { Assert.assertEquals("Albertoad", trie.remove("Albertoad")); trie.put("Albertoo", "Albertoo"); - map = trie.getPrefixedBy("X"); + map = trie.prefixMap("X"); Assert.assertEquals(2, map.size()); Assert.assertFalse(map.containsKey("Albert")); Assert.assertTrue(map.containsKey("Xavier")); @@ -793,7 +174,7 @@ public class PatriciaTrieTest { Assert.assertEquals("XyZ", iterator.next()); Assert.assertFalse(iterator.hasNext()); - map = trie.getPrefixedBy("An"); + map = trie.prefixMap("An"); Assert.assertEquals(1, map.size()); Assert.assertEquals("Anna", map.firstKey()); Assert.assertEquals("Anna", map.lastKey()); @@ -801,7 +182,7 @@ public class PatriciaTrieTest { Assert.assertEquals("Anna", iterator.next()); Assert.assertFalse(iterator.hasNext()); - map = trie.getPrefixedBy("Ban"); + map = trie.prefixMap("Ban"); Assert.assertEquals(1, map.size()); Assert.assertEquals("Banane", map.firstKey()); Assert.assertEquals("Banane", map.lastKey()); @@ -809,7 +190,7 @@ public class PatriciaTrieTest { Assert.assertEquals("Banane", iterator.next()); Assert.assertFalse(iterator.hasNext()); - map = trie.getPrefixedBy("Am"); + map = trie.prefixMap("Am"); Assert.assertFalse(map.isEmpty()); Assert.assertEquals(3, map.size()); Assert.assertEquals("Amber", trie.remove("Amber")); @@ -827,10 +208,10 @@ public class PatriciaTrieTest { Assert.assertEquals("Amber", map.firstKey()); Assert.assertEquals("Ammun", map.lastKey()); - map = trie.getPrefixedBy("Ak\0"); + map = trie.prefixMap("Ak\0"); Assert.assertTrue(map.isEmpty()); - map = trie.getPrefixedBy("Ak"); + map = trie.prefixMap("Ak"); Assert.assertEquals(2, map.size()); Assert.assertEquals("Akka", map.firstKey()); Assert.assertEquals("Akko", map.lastKey()); @@ -850,7 +231,7 @@ public class PatriciaTrieTest { Assert.assertFalse(iterator.hasNext()); Assert.assertEquals("Al", trie.remove("Al")); - map = trie.getPrefixedBy("Akka"); + map = trie.prefixMap("Akka"); Assert.assertEquals(1, map.size()); Assert.assertEquals("Akka", map.firstKey()); Assert.assertEquals("Akka", map.lastKey()); @@ -858,7 +239,7 @@ public class PatriciaTrieTest { Assert.assertEquals("Akka", iterator.next()); Assert.assertFalse(iterator.hasNext()); - map = trie.getPrefixedBy("Ab"); + map = trie.prefixMap("Ab"); Assert.assertTrue(map.isEmpty()); Assert.assertEquals(0, map.size()); try { @@ -872,7 +253,7 @@ public class PatriciaTrieTest { iterator = map.values().iterator(); Assert.assertFalse(iterator.hasNext()); - map = trie.getPrefixedBy("Albertooo"); + map = trie.prefixMap("Albertooo"); Assert.assertTrue(map.isEmpty()); Assert.assertEquals(0, map.size()); try { @@ -886,10 +267,10 @@ public class PatriciaTrieTest { iterator = map.values().iterator(); Assert.assertFalse(iterator.hasNext()); - map = trie.getPrefixedBy(""); + map = trie.prefixMap(""); Assert.assertSame(trie, map); // stricter than necessary, but a good check - map = trie.getPrefixedBy("\0"); + map = trie.prefixMap("\0"); Assert.assertTrue(map.isEmpty()); Assert.assertEquals(0, map.size()); try { @@ -904,10 +285,8 @@ public class PatriciaTrieTest { Assert.assertFalse(iterator.hasNext()); } - @Test - public void testPrefixByOffsetAndLength() { - final PatriciaTrie trie - = new PatriciaTrie(new StringKeyAnalyzer()); + public void testPrefixMapRemoval() { + final PatriciaTrie trie = new PatriciaTrie(); final String[] keys = new String[]{ "Albert", "Xavier", "XyZ", "Anna", "Alien", "Alberto", @@ -920,67 +299,7 @@ public class PatriciaTrieTest { trie.put(key, key); } - SortedMap map; - Iterator iterator; - - map = trie.getPrefixedBy("Alice", 2); - Assert.assertEquals(8, map.size()); - Assert.assertEquals("Alabama", map.firstKey()); - Assert.assertEquals("Alliese", map.lastKey()); - Assert.assertEquals("Albertoo", map.get("Albertoo")); - Assert.assertNotNull(trie.get("Xavier")); - Assert.assertNull(map.get("Xavier")); - Assert.assertNull(trie.get("Alice")); - Assert.assertNull(map.get("Alice")); - iterator = map.values().iterator(); - Assert.assertEquals("Alabama", iterator.next()); - Assert.assertEquals("Albert", iterator.next()); - Assert.assertEquals("Alberto", iterator.next()); - Assert.assertEquals("Albertoo", iterator.next()); - Assert.assertEquals("Alberts", iterator.next()); - Assert.assertEquals("Alien", iterator.next()); - Assert.assertEquals("Allie", iterator.next()); - Assert.assertEquals("Alliese", iterator.next()); - Assert.assertFalse(iterator.hasNext()); - - map = trie.getPrefixedBy("BAlice", 1, 2); - Assert.assertEquals(8, map.size()); - Assert.assertEquals("Alabama", map.firstKey()); - Assert.assertEquals("Alliese", map.lastKey()); - Assert.assertEquals("Albertoo", map.get("Albertoo")); - Assert.assertNotNull(trie.get("Xavier")); - Assert.assertNull(map.get("Xavier")); - Assert.assertNull(trie.get("Alice")); - Assert.assertNull(map.get("Alice")); - iterator = map.values().iterator(); - Assert.assertEquals("Alabama", iterator.next()); - Assert.assertEquals("Albert", iterator.next()); - Assert.assertEquals("Alberto", iterator.next()); - Assert.assertEquals("Albertoo", iterator.next()); - Assert.assertEquals("Alberts", iterator.next()); - Assert.assertEquals("Alien", iterator.next()); - Assert.assertEquals("Allie", iterator.next()); - Assert.assertEquals("Alliese", iterator.next()); - Assert.assertFalse(iterator.hasNext()); - } - - @Test - public void testPrefixedByRemoval() { - final PatriciaTrie trie - = new PatriciaTrie(new StringKeyAnalyzer()); - - final String[] keys = new String[]{ - "Albert", "Xavier", "XyZ", "Anna", "Alien", "Alberto", - "Alberts", "Allie", "Alliese", "Alabama", "Banane", - "Blabla", "Amber", "Ammun", "Akka", "Akko", "Albertoo", - "Amma" - }; - - for (final String key : keys) { - trie.put(key, key); - } - - SortedMap map = trie.getPrefixedBy("Al"); + SortedMap map = trie.prefixMap("Al"); Assert.assertEquals(8, map.size()); Iterator iter = map.keySet().iterator(); Assert.assertEquals("Alabama", iter.next()); @@ -995,7 +314,7 @@ public class PatriciaTrieTest { Assert.assertEquals("Alliese", iter.next()); Assert.assertFalse(iter.hasNext()); - map = trie.getPrefixedBy("Ak"); + map = trie.prefixMap("Ak"); Assert.assertEquals(2, map.size()); iter = map.keySet().iterator(); Assert.assertEquals("Akka", iter.next()); @@ -1008,138 +327,21 @@ public class PatriciaTrieTest { Assert.assertFalse(iter.hasNext()); } - @Test - public void testTraverseWithAllNullBitKey() { - final PatriciaTrie trie - = new PatriciaTrie(new StringKeyAnalyzer()); + //----------------------------------------------------------------------- - // - // One entry in the Trie - // Entry is stored at the root - // - - // trie.put("", "All Bits Are Zero"); - trie.put("\0", "All Bits Are Zero"); - - // - // / ("") <-- root - // \_/ \ - // null - // - - final List strings = new ArrayList(); - trie.traverse(new Cursor() { - public Decision select(final Entry entry) { - strings.add(entry.getValue()); - return Decision.CONTINUE; - } - }); - - Assert.assertEquals(1, strings.size()); - - strings.clear(); - for (final String s : trie.values()) { - strings.add(s); - } - Assert.assertEquals(1, strings.size()); + @Override + public String getCompatibilityVersion() { + return "4"; } - @Test - public void testSelectWithAllNullBitKey() { - final PatriciaTrie trie - = new PatriciaTrie(new StringKeyAnalyzer()); - - // trie.put("", "All Bits Are Zero"); - trie.put("\0", "All Bits Are Zero"); - - final List strings = new ArrayList(); - trie.select("Hello", new Cursor() { - public Decision select(final Entry entry) { - strings.add(entry.getValue()); - return Decision.CONTINUE; - } - }); - Assert.assertEquals(1, strings.size()); - } - - private static class TestCursor implements Cursor { - private final List keys; - private final List values; - private Object selectFor; - private List toRemove; - private int index = 0; - - TestCursor(final Object... objects) { - if(objects.length % 2 != 0) { - throw new IllegalArgumentException("must be * 2"); - } - - keys = new ArrayList(objects.length / 2); - values = new ArrayList(keys.size()); - toRemove = Collections.emptyList(); - for(int i = 0; i < objects.length; i++) { - keys.add(objects[i]); - values.add(objects[++i]); - } - } - - @SuppressWarnings("unused") - void selectFor(final Object object) { - selectFor = object; - } - - void addToRemove(final Object... objects) { - toRemove = new ArrayList(Arrays.asList(objects)); - } - - void remove(final Object... objects) { - for (final Object object : objects) { - final int idx = keys.indexOf(object); - keys.remove(idx); - values.remove(idx); - } - } - - void starting() { - index = 0; - } - - public void checkKey(final Object k) { - Assert.assertEquals(keys.get(index++), k); - } - - public void checkValue(final Object o) { - Assert.assertEquals(values.get(index++), o); - } - - public Decision select(final Entry entry) { - // System.out.println("Scanning: " + entry.getKey()); - Assert.assertEquals(keys.get(index), entry.getKey()); - Assert.assertEquals(values.get(index), entry.getValue()); - index++; - - if(toRemove.contains(entry.getKey())) { - // System.out.println("Removing: " + entry.getKey()); - index--; - keys.remove(index); - values.remove(index); - toRemove.remove(entry.getKey()); - return Decision.REMOVE; - } - - if(selectFor != null && selectFor.equals(entry.getKey())) { - return Decision.EXIT; - } else { - return Decision.CONTINUE; - } - } - - void finished() { - Assert.assertEquals(keys.size(), index); - } - } - - private static void assertEqualArrays(final Object[] a, final Object[] b) { - Assert.assertTrue(Arrays.equals(a, b)); - } +// public void testCreate() throws Exception { +// resetEmpty(); +// writeExternalFormToDisk( +// (java.io.Serializable) map, +// "src/test/resources/data/test/PatriciaTrie.emptyCollection.version4.obj"); +// resetFull(); +// writeExternalFormToDisk( +// (java.io.Serializable) map, +// "src/test/resources/data/test/PatriciaTrie.fullCollection.version4.obj"); +// } } diff --git a/src/test/java/org/apache/commons/collections4/trie/UnmodifiableTrieTest.java b/src/test/java/org/apache/commons/collections4/trie/UnmodifiableTrieTest.java new file mode 100644 index 000000000..28dd8630a --- /dev/null +++ b/src/test/java/org/apache/commons/collections4/trie/UnmodifiableTrieTest.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * 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 org.apache.commons.collections4.trie; + +import java.util.SortedMap; + +import junit.framework.Test; + +import org.apache.commons.collections4.BulkTest; +import org.apache.commons.collections4.Unmodifiable; +import org.apache.commons.collections4.map.AbstractSortedMapTest; +import org.apache.commons.collections4.map.UnmodifiableSortedMap; + +/** + * Extension of {@link AbstractSortedMapTest} for exercising the + * {@link UnmodifiableTrie} implementation. + * + * @since 4.0 + * @version $Id$ + */ +public class UnmodifiableTrieTest extends AbstractSortedMapTest { + + public UnmodifiableTrieTest(final String testName) { + super(testName); + } + + public static Test suite() { + return BulkTest.makeSuite(UnmodifiableTrieTest.class); + } + + //------------------------------------------------------------------- + + @Override + public SortedMap makeObject() { + return UnmodifiableSortedMap.unmodifiableSortedMap(new PatriciaTrie()); + } + + @Override + public boolean isPutChangeSupported() { + return false; + } + + @Override + public boolean isPutAddSupported() { + return false; + } + + @Override + public boolean isRemoveSupported() { + return false; + } + + @Override + public SortedMap makeFullMap() { + final SortedMap m = new PatriciaTrie(); + addSampleMappings(m); + return UnmodifiableSortedMap.unmodifiableSortedMap(m); + } + + //----------------------------------------------------------------------- + public void testUnmodifiable() { + assertTrue(makeObject() instanceof Unmodifiable); + assertTrue(makeFullMap() instanceof Unmodifiable); + } + + public void testDecorateFactory() { + final SortedMap map = makeFullMap(); + assertSame(map, UnmodifiableSortedMap.unmodifiableSortedMap(map)); + + try { + UnmodifiableSortedMap.unmodifiableSortedMap(null); + fail(); + } catch (final IllegalArgumentException ex) {} + } + + @Override + public String getCompatibilityVersion() { + return "4"; + } + +// public void testCreate() throws Exception { +// resetEmpty(); +// writeExternalFormToDisk( +// (java.io.Serializable) map, +// "src/test/resources/data/test/UnmodifiableTrie.emptyCollection.version4.obj"); +// resetFull(); +// writeExternalFormToDisk( +// (java.io.Serializable) map, +// "src/test/resources/data/test/UnmodifiableTrie.fullCollection.version4.obj"); +// } + +} diff --git a/src/test/java/org/apache/commons/collections4/trie/analyzer/ByteArrayKeyAnalyzerTest.java b/src/test/java/org/apache/commons/collections4/trie/analyzer/ByteArrayKeyAnalyzerTest.java deleted file mode 100644 index 0da5a819b..000000000 --- a/src/test/java/org/apache/commons/collections4/trie/analyzer/ByteArrayKeyAnalyzerTest.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 - * - * 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 org.apache.commons.collections4.trie.analyzer; - -import java.math.BigInteger; -import java.util.Map; -import java.util.TreeMap; - -import org.apache.commons.collections4.trie.PatriciaTrie; -import org.apache.commons.collections4.trie.analyzer.ByteArrayKeyAnalyzer; -import org.junit.Assert; -import org.junit.Test; - -public class ByteArrayKeyAnalyzerTest { - - private static final int SIZE = 20000; - - @Test - public void bitSet() { - final byte[] key = toByteArray("10100110", 2); - final ByteArrayKeyAnalyzer ka = new ByteArrayKeyAnalyzer(key.length * 8); - final int length = ka.lengthInBits(key); - - Assert.assertTrue(ka.isBitSet(key, 0, length)); - Assert.assertFalse(ka.isBitSet(key, 1, length)); - Assert.assertTrue(ka.isBitSet(key, 2, length)); - Assert.assertFalse(ka.isBitSet(key, 3, length)); - Assert.assertFalse(ka.isBitSet(key, 4, length)); - Assert.assertTrue(ka.isBitSet(key, 5, length)); - Assert.assertTrue(ka.isBitSet(key, 6, length)); - Assert.assertFalse(ka.isBitSet(key, 7, length)); - } - - @Test - public void keys() { - final PatriciaTrie trie - = new PatriciaTrie(ByteArrayKeyAnalyzer.INSTANCE); - - final Map map - = new TreeMap(ByteArrayKeyAnalyzer.INSTANCE); - - for (int i = 0; i < SIZE; i++) { - final BigInteger value = BigInteger.valueOf(i); - final byte[] key = toByteArray(value); - - final BigInteger existing = trie.put(key, value); - Assert.assertNull(existing); - - map.put(key, value); - } - - Assert.assertEquals(map.size(), trie.size()); - - for (final byte[] key : map.keySet()) { - final BigInteger expected = new BigInteger(1, key); - final BigInteger value = trie.get(key); - - Assert.assertEquals(expected, value); - } - } - - @Test - public void prefix() { - final byte[] prefix = toByteArray("00001010", 2); - final byte[] key1 = toByteArray("11001010", 2); - final byte[] key2 = toByteArray("10101100", 2); - - final ByteArrayKeyAnalyzer keyAnalyzer = new ByteArrayKeyAnalyzer(key1.length * 8); - - final int prefixLength = keyAnalyzer.lengthInBits(prefix); - - Assert.assertFalse(keyAnalyzer.isPrefix(prefix, 4, prefixLength, key1)); - Assert.assertTrue(keyAnalyzer.isPrefix(prefix, 4, prefixLength, key2)); - } - - private static byte[] toByteArray(final String value, final int radix) { - return toByteArray(Long.parseLong(value, radix)); - } - - private static byte[] toByteArray(final long value) { - return toByteArray(BigInteger.valueOf(value)); - } - - private static byte[] toByteArray(final BigInteger value) { - final byte[] src = value.toByteArray(); - if (src.length <= 1) { - return src; - } - - if (src[0] != 0) { - return src; - } - - final byte[] dst = new byte[src.length-1]; - System.arraycopy(src, 1, dst, 0, dst.length); - return dst; - } -} diff --git a/src/test/resources/data/test/PatriciaTrie.emptyCollection.version4.obj b/src/test/resources/data/test/PatriciaTrie.emptyCollection.version4.obj new file mode 100644 index 0000000000000000000000000000000000000000..4e79bb58300a990b3c91a360429d44c28a8284cc GIT binary patch literal 430 zcmZ4UmVvdnh`}(wC|xhHATc>3RWCU|H#a}87)a;jq$ZbS0@)^dB}JL3dI5<*DmgPT z1jw=7vid`0D(^cc28N0v21}y!J0=yE6eT8?Ae-aft-Lza(Ek-P*c=Oz%yG&rDbFlU z1vw|+?%X>Qu1xsC#K7poz@431>6n+8Q(2W-RKj5712j}0;yHb&=YTZAbNV39>3bve zSXUGP!^EB_wJ literal 0 HcmV?d00001 diff --git a/src/test/resources/data/test/PatriciaTrie.fullCollection.version4.obj b/src/test/resources/data/test/PatriciaTrie.fullCollection.version4.obj new file mode 100644 index 0000000000000000000000000000000000000000..5d8ad78e5a3dda8064a446ec6c5aecc6cd76fd5f GIT binary patch literal 693 zcmb7B%SyvQ6uma7ip7n;;6gVhwzyD4q;6bTu%IqnPU1KXPBSr?K2pRV5TxK*f1rXJ zK|jQ${(zvk(6ydP3hlCPa_8Lhn0qJpA5=_YDyvvHEMk6xJHBc*RhXbk$$cH*yyoaQ z;7*;P_yIcv*m@j%9rO9m0#P@njfwnKFVQjc^?1(i)#Bi&y#H=xIqUz**$H$fNVplN z{&xG?f9Zc0XsSk~GoDsMCet2|HC1W|b@S!i;c}>>b>{F!- z@EF#$@y$Um13duS$We4NG8E*95Gu(c1m~Kj1s)^?i8c<3RWCU|H#a}87)a;jq$ZbS0@)^dxrqgOp?SIaDVb@R ziAg!B!TCicsVTmR1pyOwUjBPD;srAULq!45Btt?bl@w*B>IEbMspQPW5Fp2P%jyr2 zsl4x)fF=;(60m;9q~emI#N-lWbKJX?SBDzkEQpA-8w(Xpkg&Wny`~d)=8l=$pJ~JkNTi81FF;}= z!2@^#mh44BELaNXPAj_8%{gu&qmAZmt4n)xqDMIN`x4CHuh6JqJ={C$Uy zzGaZm$0Vu0V8<$Aj_S=>Ihr`%npuC*lSDM%*Brk&MNc)nnCo%MPp6OVhxSK?21+!t z$CE<9MAG7sqS+Ec^PM&2yK4gfttqdE%dh@<7So+9_caUa-eYYbC2)IE&fJ^mAX79t z8=CGbt;ha-J#{j2dHkvEVZ$V%F-2MCGA~e+OS!JdU_vM|fm+xM?=^uP(Mm-N!n6ge z!mdg8cBMoSsD;Zp1P~(YVlfFWNEwAeSIUZ=;KWe(3q{kFv5wQEO;s)g0&^*&VBiOY zI0l+UyUs)n0mFd|f|?Mh?$aTS<6N9x4b&#(YY$LIHu$7|SuSPWO=JySV7=M=0U@0c AVgLXD literal 0 HcmV?d00001