This change adds PrefixTree. PrefixTree is used in the upcoming
MultiWordSuggestOracle to provide quick retrieval from a static set
of Strings.

Patch by: bobv, ecc, knorton
Review by: knorton



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@847 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/ui/PrefixTree.java b/user/src/com/google/gwt/user/client/ui/PrefixTree.java
new file mode 100644
index 0000000..327e4c1
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/PrefixTree.java
@@ -0,0 +1,481 @@
+/*
+ * Copyright 2006 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 com.google.gwt.user.client.ui;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+import java.util.AbstractCollection;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+/**
+ * A prefix tree (aka trie).
+ *
+ */
+class PrefixTree extends AbstractCollection {
+
+  /**
+   * Iterates over the structure of a PrefixTree. No concurrency checks are
+   * made. This Iterator will output anything new added to the tree if the new
+   * entry is after the current position of the Iterator.
+   *
+   * This Iterator implementation is iterative and uses an internal stack object
+   * to avoid call-stack limitations and invocation overhead.
+   */
+  private static class PrefixTreeIterator implements Iterator {
+
+    private JavaScriptObject stack;
+
+    /**
+     * Constructor.
+     *
+     * @param tree The base of the PrefixTree to iterate over
+     */
+    public PrefixTreeIterator(PrefixTree tree) {
+      init();
+      addTree(tree, "");
+    }
+
+    public boolean hasNext() {
+      // Have nextImpl peek at the next value that would be returned.
+      return nextImpl(true) != null;
+    }
+
+    /**
+     * {@inheritDoc} Wraps the native implementation with some sanity checking.
+     */
+    public Object next() {
+      final Object toReturn = nextImpl(false);
+
+      // A null response indicates that there was no data to be had.
+      if (toReturn == null) {
+        // Sanity check.
+        if (!hasNext()) {
+          throw new NoSuchElementException("No more elements in the iterator");
+        } else {
+          throw new RuntimeException(
+              "nextImpl() returned null, but hasNext says otherwise");
+        }
+      }
+
+      return toReturn;
+    }
+
+    public void remove() {
+      throw new UnsupportedOperationException("PrefixTree does not support "
+          + "removal.  Use clear()");
+    }
+
+    /**
+     * Add a frame to the work stack.
+     *
+     * <pre>
+     *  frame := {suffixNames, subtrees, prefix, index}
+     *  suffixNames := All suffixes in the target PrefixTree
+     *  subtrees := All subtrees in the target PrefixTree
+     *  prefix := A string that next() will prepend to output from the frame
+     *  index := Stores which suffix was last output
+     * </pre>
+     *
+     * @param tree The tree to add
+     * @param prefix The prefix to prepend to values in tree
+     */
+    private native void addTree(PrefixTree tree, String prefix) /*-{
+      var suffixes = [];
+      for (suffix in tree.@com.google.gwt.user.client.ui.PrefixTree::suffixes) {
+        suffixes.push(suffix);
+      }
+
+      var frame = {
+        suffixNames: suffixes,
+        subtrees: tree.@com.google.gwt.user.client.ui.PrefixTree::subtrees,
+        prefix: prefix,
+        index: 0
+      };
+
+      var stack = this.@com.google.gwt.user.client.ui.PrefixTree$PrefixTreeIterator::stack;
+      stack.push(frame);
+    }-*/;
+
+    /**
+     * Initialize JSNI objects.
+     */
+    private native void init() /*-{
+      this.@com.google.gwt.user.client.ui.PrefixTree$PrefixTreeIterator::stack = [];
+    }-*/;
+
+    /**
+     * Access JSNI structures.
+     *
+     * @param peek If this is true, don't advance the iteration, just return the
+     *          value that next() would return if it were called
+     * @return The next object, or null if there is an error
+     */
+    private native Object nextImpl(boolean peek) /*-{
+      var stack = this.@com.google.gwt.user.client.ui.PrefixTree$PrefixTreeIterator::stack;
+      var safe = @com.google.gwt.user.client.ui.PrefixTree::safe(Ljava/lang/String;)
+      var unsafe = @com.google.gwt.user.client.ui.PrefixTree::unsafe(Ljava/lang/String;)
+
+      // Put this in a while loop to handle descent into subtrees without recursion.
+      while (stack.length > 0) {
+        var frame = stack.pop();
+
+        // Check to see if there are any remaining suffixes to output.
+        if (frame.index < frame.suffixNames.length) {
+          var toReturn = frame.prefix + unsafe(frame.suffixNames[frame.index]);
+
+          if (!peek) {
+            frame.index++;
+          }
+
+          // If the current frame has more suffixes, retain it on the stack.
+          if (frame.index < frame.suffixNames.length) {
+            stack.push(frame);
+
+            // Otherwise, put all of the subtrees on the stack.
+          } else {
+            for (key in frame.subtrees) {
+              var target = frame.prefix + unsafe(key);
+              var subtree = frame.subtrees[key];
+              this.@com.google.gwt.user.client.ui.PrefixTree$PrefixTreeIterator::addTree(Lcom/google/gwt/user/client/ui/PrefixTree;Ljava/lang/String;)(subtree, target);
+            }
+          }
+
+          return toReturn;
+
+       // Put all subframes on the stack, and return to top of loop.
+       } else {
+         for (key in frame.subtrees) {
+           var target = frame.prefix + unsafe(key);
+           var subtree = frame.subtrees[key];
+
+           this.@com.google.gwt.user.client.ui.PrefixTree$PrefixTreeIterator::addTree(Lcom/google/gwt/user/client/ui/PrefixTree;Ljava/lang/String;)(subtree, target);
+         }
+       }
+     }
+
+     // This would indicate that next() was called on an empty iterator.
+     // Will throw an exception from next().
+     return null;
+    }-*/;
+  }
+
+  /**
+   * Used by native methods to create an appropriately blessed PrefixTree.
+   * 
+   * @param prefixLength Smaller prefix length equals faster, more direct
+   *          searches, at a cost of setup time
+   * @return a newly constructed prefix tree
+   */
+  protected static PrefixTree createPrefixTree(int prefixLength) {
+    return new PrefixTree(prefixLength);
+  }
+
+  /**
+   *  Ensure that a String can be safely used as a key to an associative-array
+   *  JavaScript object by prepending a prefix.
+   *  
+   *  @param s The String to make safe
+   *  @return A safe version of <code>s</code>
+   */
+  private static String safe(String s) {
+    return ':' + s;
+  }
+
+  /**
+   *  Undo the operation performed by safe().
+   *  
+   *  @param s A String returned from safe()
+   *  @return The original String passed into safe()
+   */
+  private static String unsafe(String s) {
+    return s.substring(1);
+  }
+
+  /**
+   * Stores the requested prefix length.
+   */
+  protected final int prefixLength;
+
+  /**
+   * Field to store terminal nodes in.
+   */
+  protected JavaScriptObject suffixes;
+
+  /**
+   * Field to store subtrees in.
+   */
+  protected JavaScriptObject subtrees;
+
+  /**
+   * Store the number of elements contained by this PrefixTree and its
+   * sub-trees.
+   */
+  protected int size = 0;
+
+  /**
+   * Constructor.
+   */
+  public PrefixTree() {
+    this(2, null);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param source Initialize from another collection
+   */
+  public PrefixTree(Collection source) {
+    this(2, source);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param prefixLength Smaller prefix length equals faster, more direct
+   *          searches, at a cost of setup time.
+   */
+  public PrefixTree(final int prefixLength) {
+    this(prefixLength, null);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param prefixLength Smaller prefix length equals faster, more direct
+   *          searches, at a cost of setup time.
+   * @param source Initialize from another collection
+   */
+  public PrefixTree(final int prefixLength, final Collection source) {
+    this.prefixLength = prefixLength;
+    clear();
+
+    if (source != null) {
+      addAll(source);
+    }
+  }
+
+  /**
+   * Adds an object to the PrefixTree.
+   *
+   * @param o The object to add
+   * @throws UnsupportedOperationException if the object is not a String
+   *  @return <code>true</code> if the string was added, <code>false</code>
+   *          otherwise
+   */
+  public boolean add(Object o) throws UnsupportedOperationException {
+    if (o instanceof String) {
+      return add((String) o);
+    } else {
+      throw new UnsupportedOperationException(
+          "Cannot add non-Strings to PrefixTree");
+    }
+  }
+
+  /**
+   * Add a String to the PrefixTree.
+   *
+   * @param s The data to add
+   * @return <code>true</code> if the string was added, <code>false</code>
+   *         otherwise
+   */
+  public native boolean add(String s) /*-{
+    var suffixes =
+        this.@com.google.gwt.user.client.ui.PrefixTree::suffixes;
+    var subtrees =
+        this.@com.google.gwt.user.client.ui.PrefixTree::subtrees;
+    var prefixLength =
+        this.@com.google.gwt.user.client.ui.PrefixTree::prefixLength;
+
+    // This would indicate a mis-use of the code.
+    if ((s == null) || (s.length == 0)) {
+      return false;
+    }
+
+    // Use <= so that strings that are exactly prefixLength long don't
+    // require some kind of null token.
+    if (s.length <= prefixLength) {
+      var safeKey = @com.google.gwt.user.client.ui.PrefixTree::safe(Ljava/lang/String;)(s);
+      if (suffixes.hasOwnProperty(safeKey)) {
+        return false;
+      } else {
+        // Each tree keeps a count of how large it and its children are.
+        this.@com.google.gwt.user.client.ui.PrefixTree::size++;
+
+        suffixes[safeKey] = true;
+        return true;
+      }
+
+    // Add the string to the appropriate PrefixTree.
+    } else {
+      var prefix = @com.google.gwt.user.client.ui.PrefixTree::safe(Ljava/lang/String;)(s.slice(0, prefixLength));
+      var theTree;
+
+      if (subtrees.hasOwnProperty(prefix)) {
+        theTree = subtrees[prefix];
+      } else {
+        theTree = @com.google.gwt.user.client.ui.PrefixTree::createPrefixTree(I)(prefixLength * 2);
+        subtrees[prefix] = theTree;
+      }
+
+      var slice = s.slice(prefixLength);
+      if (theTree.@com.google.gwt.user.client.ui.PrefixTree::add(Ljava/lang/String;)(slice)) {
+        // The size of the subtree increased, so we need to update the local count.
+        this.@com.google.gwt.user.client.ui.PrefixTree::size++;
+        return true;
+      } else {
+        return false;
+      }
+    }
+  }-*/;
+
+  /**
+   * Initialize native state.
+   */
+  public native void clear() /*-{
+    this.@com.google.gwt.user.client.ui.PrefixTree::size = 0;
+    this.@com.google.gwt.user.client.ui.PrefixTree::subtrees = {};
+    this.@com.google.gwt.user.client.ui.PrefixTree::suffixes = {};
+  }-*/;
+
+  public boolean contains(Object o) {
+    if (o instanceof String) {
+      return contains((String) o);
+    } else {
+      return false;
+    }
+  }
+
+  public boolean contains(String s) {
+    return (getSuggestions(s, 1)).contains(s);
+  }
+
+  /**
+   * Retrieve suggestions from the PrefixTree. The number of items returned from
+   * getSuggesstions may slightly exceed <code>limit</code> so that all
+   * suffixes and partial stems will be returned. This prevents the search space
+   * from changing size if the PrefixTree is used in an interactive manner.
+   * <br/> The returned List is guaranteed to be safe; changing its contents
+   * will not affect the PrefixTree.
+   *
+   * @param search The prefix to search for
+   * @param limit The desired number of results to retrieve
+   * @return A List of suggestions
+   */
+  public List/* <String> */getSuggestions(String search, int limit) {
+    final List toReturn = new ArrayList();
+    if ((search != null) && (limit > 0)) {
+      suggestImpl(search, "", toReturn, limit);
+    }
+    return toReturn;
+  }
+
+  public Iterator iterator() {
+    return new PrefixTreeIterator(this);
+  }
+
+  /**
+   * Get the number of all elements contained within the PrefixTree.
+   * 
+   * @return the size of the prefix tree
+   */
+  public int size() {
+    return size;
+  }
+
+  protected native void suggestImpl(String search, String prefix,
+      Collection output, int limit) /*-{
+    var suffixes =
+        this.@com.google.gwt.user.client.ui.PrefixTree::suffixes;
+    var subtrees =
+        this.@com.google.gwt.user.client.ui.PrefixTree::subtrees;
+    var prefixLength =
+        this.@com.google.gwt.user.client.ui.PrefixTree::prefixLength;
+
+    // Search is too big to be found in current tree, just recurse.
+    if (search.length > prefix.length + prefixLength) {
+      var key = @com.google.gwt.user.client.ui.PrefixTree::safe(Ljava/lang/String;)(search.slice(prefix.length, prefix.length + prefixLength));
+
+      // Just pick the correct subtree, if it exists, and call suggestImpl.
+      if (subtrees.hasOwnProperty(key)) {
+        var subtree = subtrees[key];
+        var target = prefix + @com.google.gwt.user.client.ui.PrefixTree::unsafe(Ljava/lang/String;)(key);
+        subtree.@com.google.gwt.user.client.ui.PrefixTree::suggestImpl(Ljava/lang/String;Ljava/lang/String;Ljava/util/Collection;I)(search, target, output, limit);
+      }
+
+    // The answer can only exist in this tree's suffixes or subtree keys.
+    } else {
+     // Check local suffixes.
+     for (suffix in suffixes) {
+       var target = prefix + @com.google.gwt.user.client.ui.PrefixTree::unsafe(Ljava/lang/String;)(suffix);
+       if (target.indexOf(search) == 0) {
+         output.@java.util.Collection::add(Ljava/lang/Object;)(target);
+       }
+
+       if (output.@java.util.Collection::size()() >= limit) {
+         return;
+       }
+     }
+
+     // Check the subtree keys.  If the key matches, that implies that all
+     // elements of the subtree would match.
+     for (var key in subtrees) {
+       var target = prefix + @com.google.gwt.user.client.ui.PrefixTree::unsafe(Ljava/lang/String;)(key);
+       var subtree = subtrees[key];
+
+       // See if the prefix gives us a match.
+       if (target.indexOf(search) == 0) {
+
+         // Provide as many suggestions as will fit into the remaining limit.
+         // If there is only one suggestion, include it.
+         if ((subtree.@com.google.gwt.user.client.ui.PrefixTree::size <= limit - output.@java.util.Collection::size()()) ||
+             (subtree.@com.google.gwt.user.client.ui.PrefixTree::size == 1)) {
+           subtree.@com.google.gwt.user.client.ui.PrefixTree::dump(Ljava/util/Collection;Ljava/lang/String;)(output, target);
+
+         // Otherwise, include as many answers as we can by truncating the suffix
+         } else {
+
+           // Always fully-specify suffixes.
+           for (var suffix in subtree.@com.google.gwt.user.client.ui.PrefixTree::suffixes) {
+             output.@java.util.Collection::add(Ljava/lang/Object;)(target + @com.google.gwt.user.client.ui.PrefixTree::unsafe(Ljava/lang/String;)(suffix));
+           }
+
+           // Give the keys of the subtree.
+           for (var subkey in subtree.@com.google.gwt.user.client.ui.PrefixTree::subtrees) {
+             output.@java.util.Collection::add(Ljava/lang/Object;)(target + @com.google.gwt.user.client.ui.PrefixTree::unsafe(Ljava/lang/String;)(subkey) + "...");
+           }
+         }
+       }
+     }
+   }
+  }-*/;
+
+  /**
+   * Put all contents of the PrefixTree into a Collection.
+   * 
+   * @param output the collection into which the prefixes will be dumped
+   * @param prefix the prefix to filter with
+   */
+  private void dump(Collection output, String prefix) {
+    for (final Iterator i = iterator(); i.hasNext();) {
+      output.add(prefix + (String) i.next());
+    }
+  }
+}
diff --git a/user/test/com/google/gwt/user/client/ui/PrefixTreeTest.java b/user/test/com/google/gwt/user/client/ui/PrefixTreeTest.java
new file mode 100644
index 0000000..0db0926
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/ui/PrefixTreeTest.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2007 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * 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 com.google.gwt.user.client.ui;
+
+import com.google.gwt.junit.client.GWTTestCase;
+
+import java.util.*;
+
+public class PrefixTreeTest extends GWTTestCase {
+  public String getModuleName() {
+    return "com.google.gwt.user.User";
+  }
+  
+  /**
+   * Ensure that names of functions declared on the Object prototype are valid
+   * data to insert into the PrefixTree
+   * http://code.google.com/p/google-web-toolkit/issues/detail?id=631
+   */
+  public void testBug631Prefixes() {
+    // Set the prefix length large enough so that we ensure prefixes are
+    // appropriately tested
+    final String[] prototypeNames = {
+        "__proto__", "constructor", "eval", "prototype", "toString",
+        "toSource", "unwatch", "valueOf",};
+
+    for (int i = 0; i < prototypeNames.length; i++) {
+      final String name = prototypeNames[i] + "AAAAAAAAAAAAAAAAAAAA";
+      final PrefixTree tree = new PrefixTree(prototypeNames[i].length());
+
+      assertFalse("Incorrectly found " + name, tree.contains(name));
+
+      assertTrue("First add() didn't return true: " + name, tree.add(name));
+      assertFalse("Second add() of duplicate entry didn't return false: "
+          + name, tree.add(name));
+
+      assertTrue("contains() didn't find added word: " + name,
+          tree.contains(name));
+
+      testSizeByIterator(tree);
+      assertTrue("PrefixTree doesn't contain the desired word",
+          1 == tree.size());
+    }
+  }
+
+  /**
+   * Ensure that names of functions declared on the Object prototype are valid
+   * data to insert into the PrefixTree
+   * http://code.google.com/p/google-web-toolkit/issues/detail?id=631
+   */
+  public void testBug631Suffixes() {
+    // Set the prefix length large enough so that we ensure suffixes are
+    // appropriately tested
+    final PrefixTree tree = new PrefixTree(100);
+    final String[] prototypeNames = {
+        "__proto__", "constructor", "eval", "prototype", "toString",
+        "toSource", "unwatch", "valueOf",};
+
+    for (int i = 0; i < prototypeNames.length; i++) {
+      final String name = prototypeNames[i];
+
+      assertFalse("Incorrectly found " + name, tree.contains(name));
+
+      assertTrue("First add() didn't return true: " + name, tree.add(name));
+      assertFalse("Second add() of duplicate entry didn't return false: "
+          + name, tree.add(name));
+
+      assertTrue("contains() didn't find added word: " + name,
+          tree.contains(name));
+    }
+
+    testSizeByIterator(tree);
+    assertTrue("PrefixTree doesn't contain all of the desired words",
+        prototypeNames.length == tree.size());
+  }
+
+  /**
+   * Tests adding multiple prefixes and clearing the contents.
+   */
+  public void testMultipleAddsAndClear() {
+    final PrefixTree tree = new PrefixTree();
+
+    assertTrue(tree.add("foo"));
+    assertFalse(tree.add("foo"));
+    assertTrue(tree.add("bar"));
+
+    assertTrue("Expecting iterator to have next", tree.iterator().hasNext());
+    assertTrue("Tree did not have expected size", tree.size() == 2);
+    testSizeByIterator(tree);
+
+    tree.clear();
+    assertFalse("Expecting cleared tree to not hasNext()",
+        tree.iterator().hasNext());
+    assertTrue("Clear did not set size to 0", tree.size() == 0);
+  }
+
+  public void testNewTree() {
+    final PrefixTree tree = new PrefixTree();
+    assertTrue("Newly-created tree had non-zero size", tree.size() == 0);
+    testSizeByIterator(tree);
+
+    assertFalse("Newly-created tree had iterator with a next element",
+        tree.iterator().hasNext());
+  }
+
+  /**
+   * Tests newly constructed prefix tree assumptions.
+   */
+  public void testPlaysWellWithOthers() {
+    final List l = new ArrayList();
+    for (int i = 0; i < 100; i++) {
+      l.add(String.valueOf(i));
+    }
+
+    final PrefixTree tree = new PrefixTree();
+    tree.addAll(l);
+
+    assertTrue("Not all elements copied", tree.size() == l.size());
+    testSizeByIterator(tree);
+
+    assertTrue("Original list does not contain all of the tree",
+        l.containsAll(tree));
+
+    assertTrue("The tree does not contain the original list",
+        tree.containsAll(l));
+  }
+
+  /**
+   * Test whether the prefix tree works appropriately with collections.
+   */
+  public void testSuggestions() {
+    final PrefixTree tree = new PrefixTree();
+
+    assertTrue(tree.add("onetwothree"));
+    assertTrue(tree.add("onetwothree1"));
+    assertTrue(tree.add("onetwothree2"));
+    assertTrue(tree.add("onetwothree3"));
+    assertTrue(tree.add("fourfivesix"));
+    assertTrue(tree.add("fourfivesix1"));
+    assertTrue(tree.add("fourfivesix2"));
+    assertTrue(tree.add("fourfivesix3"));
+    assertTrue(tree.add("seveneightnine"));
+    assertTrue(tree.add("seveneightnine1"));
+    assertTrue(tree.add("seveneightnine2"));
+    assertTrue(tree.add("seveneightnine3"));
+    assertTrue(tree.add("somethingdifferent"));
+
+    assertTrue(tree.size() == 13);
+    testSizeByIterator(tree);
+    assertTrue(tree.iterator().hasNext());
+
+    List l;
+
+    l = tree.getSuggestions("", 13);
+    assertTrue("Expected size of 13, got " + l.size(), l.size() == 13);
+    assertAllStartWith(l, "");
+
+    l = tree.getSuggestions("one", 10);
+    assertTrue("Expected size of 4, got " + l.size(), l.size() == 4);
+    assertAllStartWith(l, "one");
+
+    l = tree.getSuggestions("onetwothree", 10);
+    assertTrue("Expected size of 4, got " + l.size(), l.size() == 4);
+    assertAllStartWith(l, "onetwothree");
+
+    l = tree.getSuggestions("onetwothree1", 10);
+    assertTrue("Expected size of 1, got " + l.size(), l.size() == 1);
+    assertAllStartWith(l, "onetwothree1");
+
+    l = tree.getSuggestions("o", 1);
+    assertTrue("Expected size of 1, got " + l.size(), l.size() == 1);
+    assertTrue(((String) l.get(0)).endsWith("..."));
+    assertAllStartWith(l, "o");
+
+    l = tree.getSuggestions("something", 1);
+    assertTrue("Expected size of 1, got " + l.size(), l.size() == 1);
+    assertEquals("somethingdifferent", l.get(0));
+    assertAllStartWith(l, "somethingdifferent");
+  }
+
+  protected void assertAllStartWith(List l, String prefix) {
+    for (final Iterator i = l.iterator(); i.hasNext();) {
+      final String test = (String) i.next();
+      assertTrue(test + " does not start with " + prefix,
+          test.startsWith(prefix));
+    }
+  }
+
+  /**
+   * Ensure that size() reports the same number of items that the iterator
+   * produces.
+   * 
+   * @param tree the tree to test
+   */
+  protected void testSizeByIterator(PrefixTree tree) {
+    int count = 0;
+    for (final Iterator i = tree.iterator(); i.hasNext();) {
+      i.next();
+      count++;
+    }
+
+    assertTrue("Iteration count " + count + " did not match size "
+        + tree.size(), count == tree.size());
+  }
+}