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 000000000..4e79bb583 Binary files /dev/null and b/src/test/resources/data/test/PatriciaTrie.emptyCollection.version4.obj differ 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 000000000..5d8ad78e5 Binary files /dev/null and b/src/test/resources/data/test/PatriciaTrie.fullCollection.version4.obj differ diff --git a/src/test/resources/data/test/UnmodifiableTrie.emptyCollection.version4.obj b/src/test/resources/data/test/UnmodifiableTrie.emptyCollection.version4.obj new file mode 100644 index 000000000..1286510d2 Binary files /dev/null and b/src/test/resources/data/test/UnmodifiableTrie.emptyCollection.version4.obj differ diff --git a/src/test/resources/data/test/UnmodifiableTrie.fullCollection.version4.obj b/src/test/resources/data/test/UnmodifiableTrie.fullCollection.version4.obj new file mode 100644 index 000000000..a96ddffb1 Binary files /dev/null and b/src/test/resources/data/test/UnmodifiableTrie.fullCollection.version4.obj differ