1 package org.apache.lucene.search.suggest.jaspell;
4 * Copyright (c) 2005 Bruno Martins
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the organization nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
29 * THE POSSIBILITY OF SUCH DAMAGE.
32 import java.io.BufferedReader;
34 import java.io.FileInputStream;
35 import java.io.IOException;
36 import java.io.InputStreamReader;
37 import java.util.List;
38 import java.util.Vector;
39 import java.util.zip.GZIPInputStream;
42 * Implementation of a Ternary Search Trie, a data structure for storing
43 * <code>String</code> objects that combines the compact size of a binary search
44 * tree with the speed of a digital search trie, and is therefore ideal for
45 * practical use in sorting and searching data.</p>
48 * This data structure is faster than hashing for many typical search problems,
49 * and supports a broader range of useful problems and operations. Ternary
50 * searches are faster than hashing and more powerful, too.
54 * The theory of ternary search trees was described at a symposium in 1997 (see
55 * "Fast Algorithms for Sorting and Searching Strings," by J.L. Bentley and R.
56 * Sedgewick, Proceedings of the 8th Annual ACM-SIAM Symposium on Discrete
57 * Algorithms, January 1997). Algorithms in C, Third Edition, by Robert
58 * Sedgewick (Addison-Wesley, 1998) provides yet another view of ternary search
61 * @author Bruno Martins
64 public class JaspellTernarySearchTrie {
67 * An inner class of Ternary Search Trie that represents a node in the trie.
69 protected final class TSTNode {
71 /** Index values for accessing relatives array. */
72 protected final static int PARENT = 0, LOKID = 1, EQKID = 2, HIKID = 3;
74 /** The key to the node. */
75 protected Object data;
77 /** The relative nodes. */
78 protected TSTNode[] relatives = new TSTNode[4];
80 /** The char used in the split. */
81 protected char splitchar;
87 * The char used in the split.
91 protected TSTNode(char splitchar, TSTNode parent) {
92 this.splitchar = splitchar;
93 relatives[PARENT] = parent;
98 * Compares characters by alfabetical order.
101 * The first char in the comparison.
103 * The second char in the comparison.
104 *@return A negative number, 0 or a positive number if the second char is
105 * less, equal or greater.
107 private static int compareCharsAlphabetically(char cCompare2, char cRef) {
108 return Character.toLowerCase(cCompare2) - Character.toLowerCase(cRef);
111 /* what follows is the original Jaspell code.
112 private static int compareCharsAlphabetically(int cCompare2, int cRef) {
114 if (cCompare2 >= 65) {
115 if (cCompare2 < 89) {
116 cCompare = (2 * cCompare2) - 65;
117 } else if (cCompare2 < 97) {
118 cCompare = cCompare2 + 24;
119 } else if (cCompare2 < 121) {
120 cCompare = (2 * cCompare2) - 128;
121 } else cCompare = cCompare2;
122 } else cCompare = cCompare2;
124 return cCompare - cRef;
127 return cCompare - ((2 * cRef) - 65);
130 return cCompare - (cRef + 24);
133 return cCompare - ((2 * cRef) - 128);
135 return cCompare - cRef;
140 * The default number of values returned by the <code>matchAlmost</code>
143 private int defaultNumReturnValues = -1;
146 * the number of differences allowed in a call to the
147 * <code>matchAlmostKey</code> method.
149 private int matchAlmostDiff;
151 /** The base node in the trie. */
152 private TSTNode rootNode;
155 * Constructs an empty Ternary Search Trie.
157 public JaspellTernarySearchTrie() {
161 void setRoot(TSTNode newRoot) {
171 * Constructs a Ternary Search Trie and loads data from a <code>File</code>
172 * into the Trie. The file is a normal text document, where each line is of
173 * the form word TAB float.
176 * The <code>File</code> with the data to load into the Trie.
177 *@exception IOException
178 * A problem occured while reading the data.
180 public JaspellTernarySearchTrie(File file) throws IOException {
185 * Constructs a Ternary Search Trie and loads data from a <code>File</code>
186 * into the Trie. The file is a normal text document, where each line is of
187 * the form "word TAB float".
190 * The <code>File</code> with the data to load into the Trie.
192 * If true, the file is compressed with the GZIP algorithm, and if
193 * false, the file is a normal text document.
194 *@exception IOException
195 * A problem occured while reading the data.
197 public JaspellTernarySearchTrie(File file, boolean compression)
202 in = new BufferedReader(new InputStreamReader(new GZIPInputStream(
203 new FileInputStream(file))));
204 else in = new BufferedReader(new InputStreamReader((new FileInputStream(
208 Float occur, one = new Float(1);
210 while ((word = in.readLine()) != null) {
212 pos = word.indexOf("\t");
215 occur = Float.parseFloat(word.substring(pos + 1).trim());
216 word = word.substring(0, pos);
218 String key = word.toLowerCase();
219 if (rootNode == null) {
220 rootNode = new TSTNode(key.charAt(0), null);
223 if (key.length() > 0 && rootNode != null) {
224 TSTNode currentNode = rootNode;
227 if (currentNode == null) break;
228 int charComp = compareCharsAlphabetically(key.charAt(charIndex),
229 currentNode.splitchar);
232 if (charIndex == key.length()) {
236 currentNode = currentNode.relatives[TSTNode.EQKID];
237 } else if (charComp < 0) {
238 currentNode = currentNode.relatives[TSTNode.LOKID];
240 currentNode = currentNode.relatives[TSTNode.HIKID];
244 if (node != null) occur2 = ((Float) (node.data));
245 if (occur2 != null) {
246 occur += occur2.floatValue();
248 currentNode = getOrCreateNode(word.trim().toLowerCase());
249 currentNode.data = occur;
256 * Deletes the node passed in as an argument. If this node has non-null data,
257 * then both the node and the data will be deleted. It also deletes any other
258 * nodes in the trie that are no longer needed after the deletion of the node.
261 * The node to delete.
263 private void deleteNode(TSTNode nodeToDelete) {
264 if (nodeToDelete == null) {
267 nodeToDelete.data = null;
268 while (nodeToDelete != null) {
269 nodeToDelete = deleteNodeRecursion(nodeToDelete);
270 // deleteNodeRecursion(nodeToDelete);
275 * Recursively visits each node to be deleted.
277 * To delete a node, first set its data to null, then pass it into this
278 * method, then pass the node returned by this method into this method (make
279 * sure you don't delete the data of any of the nodes returned from this
280 * method!) and continue in this fashion until the node returned by this
281 * method is <code>null</code>.
283 * The TSTNode instance returned by this method will be next node to be
284 * operated on by <code>deleteNodeRecursion</code> (This emulates recursive
285 * method call while avoiding the JVM overhead normally associated with a
289 * The node to delete.
290 *@return The next node to be called in deleteNodeRecursion.
292 private TSTNode deleteNodeRecursion(TSTNode currentNode) {
293 if (currentNode == null) {
296 if (currentNode.relatives[TSTNode.EQKID] != null
297 || currentNode.data != null) {
300 // can't delete this node if it has a non-null eq kid or data
301 TSTNode currentParent = currentNode.relatives[TSTNode.PARENT];
302 boolean lokidNull = currentNode.relatives[TSTNode.LOKID] == null;
303 boolean hikidNull = currentNode.relatives[TSTNode.HIKID] == null;
305 if (currentParent.relatives[TSTNode.LOKID] == currentNode) {
306 childType = TSTNode.LOKID;
307 } else if (currentParent.relatives[TSTNode.EQKID] == currentNode) {
308 childType = TSTNode.EQKID;
309 } else if (currentParent.relatives[TSTNode.HIKID] == currentNode) {
310 childType = TSTNode.HIKID;
315 if (lokidNull && hikidNull) {
316 currentParent.relatives[childType] = null;
317 return currentParent;
320 currentParent.relatives[childType] = currentNode.relatives[TSTNode.HIKID];
321 currentNode.relatives[TSTNode.HIKID].relatives[TSTNode.PARENT] = currentParent;
322 return currentParent;
325 currentParent.relatives[childType] = currentNode.relatives[TSTNode.LOKID];
326 currentNode.relatives[TSTNode.LOKID].relatives[TSTNode.PARENT] = currentParent;
327 return currentParent;
329 int deltaHi = currentNode.relatives[TSTNode.HIKID].splitchar
330 - currentNode.splitchar;
331 int deltaLo = currentNode.splitchar
332 - currentNode.relatives[TSTNode.LOKID].splitchar;
335 if (deltaHi == deltaLo) {
336 if (Math.random() < 0.5) {
342 if (deltaHi > deltaLo) {
343 movingKid = TSTNode.HIKID;
344 targetNode = currentNode.relatives[TSTNode.LOKID];
346 movingKid = TSTNode.LOKID;
347 targetNode = currentNode.relatives[TSTNode.HIKID];
349 while (targetNode.relatives[movingKid] != null) {
350 targetNode = targetNode.relatives[movingKid];
352 targetNode.relatives[movingKid] = currentNode.relatives[movingKid];
353 currentParent.relatives[childType] = targetNode;
354 targetNode.relatives[TSTNode.PARENT] = currentParent;
356 currentNode.relatives[TSTNode.LOKID] = null;
359 currentNode.relatives[TSTNode.HIKID] = null;
361 return currentParent;
365 * Retrieve the object indexed by a key.
368 * A <code>String</code> index.
369 *@return The object retrieved from the Ternary Search Trie.
371 public Object get(String key) {
372 TSTNode node = getNode(key.trim().toLowerCase());
380 * Retrieve the <code>Float</code> indexed by key, increment it by one unit
381 * and store the new <code>Float</code>.
384 * A <code>String</code> index.
385 *@return The <code>Float</code> retrieved from the Ternary Search Trie.
387 public Float getAndIncrement(String key) {
388 String key2 = key.trim().toLowerCase();
389 TSTNode node = getNode(key2);
393 Float aux = (Float) (node.data);
397 aux = new Float(aux.intValue() + 1);
404 * Returns the key that indexes the node argument.
407 * The node whose index is to be calculated.
408 *@return The <code>String</code> that indexes the node argument.
410 protected String getKey(TSTNode node) {
411 StringBuffer getKeyBuffer = new StringBuffer();
412 getKeyBuffer.setLength(0);
413 getKeyBuffer.append("" + node.splitchar);
416 currentNode = node.relatives[TSTNode.PARENT];
418 while (currentNode != null) {
419 if (currentNode.relatives[TSTNode.EQKID] == lastNode) {
420 getKeyBuffer.append("" + currentNode.splitchar);
422 lastNode = currentNode;
423 currentNode = currentNode.relatives[TSTNode.PARENT];
425 getKeyBuffer.reverse();
426 return getKeyBuffer.toString();
430 * Returns the node indexed by key, or <code>null</code> if that node doesn't
431 * exist. Search begins at root node.
434 * A <code>String</code> that indexes the node that is returned.
435 *@return The node object indexed by key. This object is an instance of an
436 * inner class named <code>TernarySearchTrie.TSTNode</code>.
438 public TSTNode getNode(String key) {
439 return getNode(key, rootNode);
443 * Returns the node indexed by key, or <code>null</code> if that node doesn't
444 * exist. The search begins at root node.
447 * A <code>String</code> that indexes the node that is returned.
449 * The top node defining the subtrie to be searched.
450 *@return The node object indexed by key. This object is an instance of an
451 * inner class named <code>TernarySearchTrie.TSTNode</code>.
453 protected TSTNode getNode(String key2, TSTNode startNode) {
454 String key = key2.trim().toLowerCase();
455 if (key == null || startNode == null || key.length() == 0) {
458 TSTNode currentNode = startNode;
461 if (currentNode == null) {
464 int charComp = compareCharsAlphabetically(key.charAt(charIndex),
465 currentNode.splitchar);
468 if (charIndex == key.length()) {
471 currentNode = currentNode.relatives[TSTNode.EQKID];
472 } else if (charComp < 0) {
473 currentNode = currentNode.relatives[TSTNode.LOKID];
475 currentNode = currentNode.relatives[TSTNode.HIKID];
481 * Returns the node indexed by key, creating that node if it doesn't exist,
482 * and creating any required intermediate nodes if they don't exist.
485 * A <code>String</code> that indexes the node that is returned.
486 *@return The node object indexed by key. This object is an instance of an
487 * inner class named <code>TernarySearchTrie.TSTNode</code>.
488 *@exception NullPointerException
489 * If the key is <code>null</code>.
490 *@exception IllegalArgumentException
491 * If the key is an empty <code>String</code>.
493 protected TSTNode getOrCreateNode(String key) throws NullPointerException,
494 IllegalArgumentException {
496 throw new NullPointerException(
497 "attempt to get or create node with null key");
499 if (key.length() == 0) {
500 throw new IllegalArgumentException(
501 "attempt to get or create node with key of zero length");
503 if (rootNode == null) {
504 rootNode = new TSTNode(key.charAt(0), null);
506 TSTNode currentNode = rootNode;
509 int charComp = compareCharsAlphabetically(key.charAt(charIndex),
510 currentNode.splitchar);
513 if (charIndex == key.length()) {
516 if (currentNode.relatives[TSTNode.EQKID] == null) {
517 currentNode.relatives[TSTNode.EQKID] = new TSTNode(key
518 .charAt(charIndex), currentNode);
520 currentNode = currentNode.relatives[TSTNode.EQKID];
521 } else if (charComp < 0) {
522 if (currentNode.relatives[TSTNode.LOKID] == null) {
523 currentNode.relatives[TSTNode.LOKID] = new TSTNode(key
524 .charAt(charIndex), currentNode);
526 currentNode = currentNode.relatives[TSTNode.LOKID];
528 if (currentNode.relatives[TSTNode.HIKID] == null) {
529 currentNode.relatives[TSTNode.HIKID] = new TSTNode(key
530 .charAt(charIndex), currentNode);
532 currentNode = currentNode.relatives[TSTNode.HIKID];
538 * Returns a <code>List</code> of keys that almost match the argument key.
539 * Keys returned will have exactly diff characters that do not match the
540 * target key, where diff is equal to the last value passed in as an argument
541 * to the <code>setMatchAlmostDiff</code> method.
543 * If the <code>matchAlmost</code> method is called before the
544 * <code>setMatchAlmostDiff</code> method has been called for the first time,
549 *@return A <code>List</code> with the results.
551 public List<String> matchAlmost(String key) {
552 return matchAlmost(key, defaultNumReturnValues);
556 * Returns a <code>List</code> of keys that almost match the argument key.
557 * Keys returned will have exactly diff characters that do not match the
558 * target key, where diff is equal to the last value passed in as an argument
559 * to the <code>setMatchAlmostDiff</code> method.
561 * If the <code>matchAlmost</code> method is called before the
562 * <code>setMatchAlmostDiff</code> method has been called for the first time,
567 *@param numReturnValues
568 * The maximum number of values returned by this method.
569 *@return A <code>List</code> with the results
571 public List<String> matchAlmost(String key, int numReturnValues) {
572 return matchAlmostRecursion(rootNode, 0, matchAlmostDiff, key,
573 ((numReturnValues < 0) ? -1 : numReturnValues), new Vector<String>(), false);
577 * Recursivelly vists the nodes in order to find the ones that almost match a
585 * The number of differences so far.
586 *@param matchAlmostNumReturnValues
587 * The maximum number of values in the result <code>List</code>.
588 *@param matchAlmostResult2
589 * The results so far.
591 * If true all keys having up to and including matchAlmostDiff
592 * mismatched letters will be included in the result (including a key
593 * that is exactly the same as the target string) otherwise keys will
594 * be included in the result only if they have exactly
595 * matchAlmostDiff number of mismatched letters.
596 *@param matchAlmostKey
597 * The key being searched.
598 *@return A <code>List</code> with the results.
600 private List<String> matchAlmostRecursion(TSTNode currentNode, int charIndex,
601 int d, String matchAlmostKey, int matchAlmostNumReturnValues,
602 List<String> matchAlmostResult2, boolean upTo) {
603 if ((currentNode == null)
604 || (matchAlmostNumReturnValues != -1 && matchAlmostResult2.size() >= matchAlmostNumReturnValues)
605 || (d < 0) || (charIndex >= matchAlmostKey.length())) {
606 return matchAlmostResult2;
608 int charComp = compareCharsAlphabetically(matchAlmostKey.charAt(charIndex),
609 currentNode.splitchar);
610 List<String> matchAlmostResult = matchAlmostResult2;
611 if ((d > 0) || (charComp < 0)) {
612 matchAlmostResult = matchAlmostRecursion(
613 currentNode.relatives[TSTNode.LOKID], charIndex, d,
614 matchAlmostKey, matchAlmostNumReturnValues, matchAlmostResult,
617 int nextD = (charComp == 0) ? d : d - 1;
618 boolean cond = (upTo) ? (nextD >= 0) : (nextD == 0);
619 if ((matchAlmostKey.length() == charIndex + 1) && cond
620 && (currentNode.data != null)) {
621 matchAlmostResult.add(getKey(currentNode));
623 matchAlmostResult = matchAlmostRecursion(
624 currentNode.relatives[TSTNode.EQKID], charIndex + 1, nextD,
625 matchAlmostKey, matchAlmostNumReturnValues, matchAlmostResult, upTo);
626 if ((d > 0) || (charComp > 0)) {
627 matchAlmostResult = matchAlmostRecursion(
628 currentNode.relatives[TSTNode.HIKID], charIndex, d,
629 matchAlmostKey, matchAlmostNumReturnValues, matchAlmostResult,
632 return matchAlmostResult;
636 * Returns an alphabetical <code>List</code> of all keys in the trie that
637 * begin with a given prefix. Only keys for nodes having non-null data are
638 * included in the <code>List</code>.
641 * Each key returned from this method will begin with the characters
643 *@return A <code>List</code> with the results.
645 public List<String> matchPrefix(String prefix) {
646 return matchPrefix(prefix, defaultNumReturnValues);
650 * Returns an alphabetical <code>List</code> of all keys in the trie that
651 * begin with a given prefix. Only keys for nodes having non-null data are
652 * included in the <code>List</code>.
655 * Each key returned from this method will begin with the characters
657 *@param numReturnValues
658 * The maximum number of values returned from this method.
659 *@return A <code>List</code> with the results
661 public List<String> matchPrefix(String prefix, int numReturnValues) {
662 Vector<String> sortKeysResult = new Vector<String>();
663 TSTNode startNode = getNode(prefix);
664 if (startNode == null) {
665 return sortKeysResult;
667 if (startNode.data != null) {
668 sortKeysResult.addElement(getKey(startNode));
670 return sortKeysRecursion(startNode.relatives[TSTNode.EQKID],
671 ((numReturnValues < 0) ? -1 : numReturnValues), sortKeysResult);
675 * Returns the number of nodes in the trie that have non-null data.
677 *@return The number of nodes in the trie that have non-null data.
679 public int numDataNodes() {
680 return numDataNodes(rootNode);
684 * Returns the number of nodes in the subtrie below and including the starting
685 * node. The method counts only nodes that have non-null data.
688 * The top node of the subtrie. the node that defines the subtrie.
689 *@return The total number of nodes in the subtrie.
691 protected int numDataNodes(TSTNode startingNode) {
692 return recursiveNodeCalculator(startingNode, true, 0);
696 * Returns the total number of nodes in the trie. The method counts nodes
697 * whether or not they have data.
699 *@return The total number of nodes in the trie.
701 public int numNodes() {
702 return numNodes(rootNode);
706 * Returns the total number of nodes in the subtrie below and including the
707 * starting Node. The method counts nodes whether or not they have data.
710 * The top node of the subtrie. The node that defines the subtrie.
711 *@return The total number of nodes in the subtrie.
713 protected int numNodes(TSTNode startingNode) {
714 return recursiveNodeCalculator(startingNode, false, 0);
718 * Stores a value in the trie. The value may be retrieved using the key.
721 * A <code>String</code> that indexes the object to be stored.
723 * The object to be stored in the Trie.
725 public void put(String key, Object value) {
726 getOrCreateNode(key.trim().toLowerCase()).data = value;
730 * Recursivelly visists each node to calculate the number of nodes.
735 * If true we check the data to be different of <code>null</code>.
737 * The number of nodes so far.
738 *@return The number of nodes accounted.
740 private int recursiveNodeCalculator(TSTNode currentNode, boolean checkData,
742 if (currentNode == null) {
745 int numNodes = recursiveNodeCalculator(
746 currentNode.relatives[TSTNode.LOKID], checkData, numNodes2);
747 numNodes = recursiveNodeCalculator(currentNode.relatives[TSTNode.EQKID],
748 checkData, numNodes);
749 numNodes = recursiveNodeCalculator(currentNode.relatives[TSTNode.HIKID],
750 checkData, numNodes);
752 if (currentNode.data != null) {
762 * Removes the value indexed by key. Also removes all nodes that are rendered
763 * unnecessary by the removal of this data.
766 * A <code>string</code> that indexes the object to be removed from
769 public void remove(String key) {
770 deleteNode(getNode(key.trim().toLowerCase()));
774 * Sets the number of characters by which words can differ from target word
775 * when calling the <code>matchAlmost</code> method.
777 * Arguments less than 0 will set the char difference to 0, and arguments
778 * greater than 3 will set the char difference to 3.
781 * The number of characters by which words can differ from target
784 public void setMatchAlmostDiff(int diff) {
787 } else if (diff > 3) {
790 matchAlmostDiff = diff;
795 * Sets the default maximum number of values returned from the
796 * <code>matchPrefix</code> and <code>matchAlmost</code> methods.
798 * The value should be set this to -1 to get an unlimited number of return
799 * values. note that the methods mentioned above provide overloaded versions
800 * that allow you to specify the maximum number of return values, in which
801 * case this value is temporarily overridden.
804 * The number of values that will be returned when calling the
807 public void setNumReturnValues(int num) {
808 defaultNumReturnValues = (num < 0) ? -1 : num;
812 * Returns keys sorted in alphabetical order. This includes the start Node and
813 * all nodes connected to the start Node.
815 * The number of keys returned is limited to numReturnValues. To get a list
816 * that isn't limited in size, set numReturnValues to -1.
819 * The top node defining the subtrie to be searched.
820 *@param numReturnValues
821 * The maximum number of values returned from this method.
822 *@return A <code>List</code> with the results.
824 protected List<String> sortKeys(TSTNode startNode, int numReturnValues) {
825 return sortKeysRecursion(startNode, ((numReturnValues < 0) ? -1
826 : numReturnValues), new Vector<String>());
830 * Returns keys sorted in alphabetical order. This includes the current Node
831 * and all nodes connected to the current Node.
833 * Sorted keys will be appended to the end of the resulting <code>List</code>.
834 * The result may be empty when this method is invoked, but may not be
839 *@param sortKeysNumReturnValues
840 * The maximum number of values in the result.
841 *@param sortKeysResult2
842 * The results so far.
843 *@return A <code>List</code> with the results.
845 private List<String> sortKeysRecursion(TSTNode currentNode,
846 int sortKeysNumReturnValues, List<String> sortKeysResult2) {
847 if (currentNode == null) {
848 return sortKeysResult2;
850 List<String> sortKeysResult = sortKeysRecursion(
851 currentNode.relatives[TSTNode.LOKID], sortKeysNumReturnValues,
853 if (sortKeysNumReturnValues != -1
854 && sortKeysResult.size() >= sortKeysNumReturnValues) {
855 return sortKeysResult;
857 if (currentNode.data != null) {
858 sortKeysResult.add(getKey(currentNode));
860 sortKeysResult = sortKeysRecursion(currentNode.relatives[TSTNode.EQKID],
861 sortKeysNumReturnValues, sortKeysResult);
862 return sortKeysRecursion(currentNode.relatives[TSTNode.HIKID],
863 sortKeysNumReturnValues, sortKeysResult);