/*
* Copyright (c) 2001-2007 Sun Microsystems, Inc. All rights reserved.
*
* The Sun Project JXTA(TM) Software License
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The end-user documentation included with the redistribution, if any, must
* include the following acknowledgment: "This product includes software
* developed by Sun Microsystems, Inc. for JXTA(TM) technology."
* Alternately, this acknowledgment may appear in the software itself, if
* and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must
* not be used to endorse or promote products derived from this software
* without prior written permission. For written permission, please contact
* Project JXTA at http://www.jxta.org.
*
* 5. Products derived from this software may not be called "JXTA", nor may
* "JXTA" appear in their name, without prior written permission of Sun.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN
* MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* JXTA is a registered trademark of Sun Microsystems, Inc. in the United
* States and other countries.
*
* Please see the license information page at :
* <http://www.jxta.org/project/www/license.html> for instructions on use of
* the license in source files.
*
* ====================================================================
*
* This software consists of voluntary contributions made by many individuals
* on behalf of Project JXTA. For more information on Project JXTA, please see
* http://www.jxta.org.
*
* This license is based on the BSD license adopted by the Apache Foundation.
*/
package net.jxta.impl.util.ternary;
/**
* Implementation of ternary search tree. A Ternary Search Tree is a data structure that behaves in a manner that is very similar to a HashMap.
* Author Wally Flint: wally@wallyflint.com
*
* Lifted from the Java World article: http://www.javaworld.com/javaworld/jw-02-2001/jw-0216-ternary.html
* "With thanks to Michael Amster of webeasy.com for introducing me to the Ternary Search Tree, and providing some starting code."
*
* SimonT: Added interface and a fix for NPE on final node removal (left as comment to WEB article; contributor unknown).
*/
public class TernarySearchTreeImpl implements TernarySearchTree {
private TSTNode rootNode;
private int defaultNumReturnValues = -1;
private int lastNumberOfReturnValues; // convenience variable for matchPrefix and sortKeys methods
private DoublyLinkedList sortKeysResult; // convenience variable for matchPrefix and sortKeys methods
private boolean sortKeysList; // convenience variable for matchPrefix, matchPrefixString and sortKeys methods
private StringBuffer sortKeysBuffer; // convenience variable for matchPrefixString and sortKeys methods
private int sortKeysNumReturnValues; // convenience variable for matchPrefix and sortKeys methods
private DoublyLinkedList matchAlmostResult; // convenience variable for matchAlmost
private StringBuffer matchAlmostBuffer; // convenience variable for matchAlmostString
private boolean matchAlmostListAction; // convenience variable for matchAlmost and matchAlmostString
private int matchAlmostNumReturnValues; // convenience variable for matchAlmost
private String matchAlmostKey; // convenience variable for matchAlmost
private int matchAlmostDiff; // convenience variable for matchAlmost
private int maxMatchAlmostDiff = 4; // convenience variable for matchAlmost
private StringBuffer getKeyBuffer = new StringBuffer( ); // convenience variable for getKey method
private int numNodes; // convenience variable for numNodes methods
private boolean checkData; // convenience variable for numNodes methods
/* (non-Javadoc)
* @see net.jxta.impl.util.ternary.ITernarySearchTree#put(java.lang.String, java.lang.Object)
*/
public void put( String key, Object value ) {
getOrCreateNode( key ).data = value;
}
/* (non-Javadoc)
* @see net.jxta.impl.util.ternary.ITernarySearchTree#get(java.lang.String)
*/
public Object get( String key ) {
TSTNode node = getNode( key );
if ( node == null ) {
return null;
}
return node.data;
}
/* (non-Javadoc)
* @see net.jxta.impl.util.ternary.ITernarySearchTree#remove(java.lang.String)
*/
public void remove( String key ) {
deleteNode( getNode( key ) );
}
/* (non-Javadoc)
* @see net.jxta.impl.util.ternary.TernarySearchTree#setNumReturnValues(int)
*/
public void setNumReturnValues( int num ) {
defaultNumReturnValues = ( num < 0 ) ? ( -1 ) : num;
}
private int checkNumberOfReturnValues( int numReturnValues ) {
return ( ( numReturnValues < 0 ) ? ( -1 ) : numReturnValues );
}
/** Returns the number of values returned by the last call to matchAlmostString or matchPrefixString methods.
* (This is really just for the purposes of the demo applet.)
*/
public int getLastNumReturnValues( ) {
return lastNumberOfReturnValues;
}
/** Returns the Node indexed by key, or null if that node doesn't exist. Search begins at root node.
* @param key An index that points to the desired node.
* @return TSTNode The node object indexed by key. This object is an instance of an inner class
* named TernarySearchTree.TSTNode.
*/
public TSTNode getNode( String key ) {
return getNode( key, rootNode );
}
/** Returns the Node indexed by key, or null if that node doesn't exist. Search begins at root node.
* @param key An index that points to the desired node.
* @param startNode The top node defining the subtree to be searched.
* @return TSTNode The node object indexed by key. This object is an instance of an inner class
* named TernarySearchTree.TSTNode.
*/
protected TSTNode getNode( String key, TSTNode startNode ) {
if ( ( key == null ) || ( startNode == null ) || ( key.length( ) == 0 ) ) {
return null;
}
TSTNode currentNode = startNode;
int charIndex = 0;
while ( true ) {
if ( currentNode == null ) {
return null;
}
int charComp = CharUtility.compareCharsAlphabetically( key.charAt( charIndex ), currentNode.splitchar );
if ( charComp == 0 ) {
charIndex++;
if ( charIndex == key.length( ) ) {
return currentNode;
}
currentNode = currentNode.relatives [ TSTNode.EQKID ];
} else if ( charComp < 0 ) {
currentNode = currentNode.relatives [ TSTNode.LOKID ];
} else {
// charComp must be greater than zero
currentNode = currentNode.relatives [ TSTNode.HIKID ];
}
}
}
/* (non-Javadoc)
* @see net.jxta.impl.util.ternary.ITernarySearchTree#matchPrefix(java.lang.String)
*/
public DoublyLinkedList matchPrefix( String prefix ) {
return matchPrefix( prefix, defaultNumReturnValues );
}
/** Returns alphabetical list of all keys in the tree that begin with prefix. Only keys for nodes having non-null data
* are included in the list.
* @param prefix Each key returned from this method will begin with the characters in prefix.
* @param numReturnValues The maximum number of values returned from this method.
* @return DoublyLinkedList An implementation of a LinkedList that is java 1.1 compatible.
*/
public DoublyLinkedList matchPrefix( String prefix, int numReturnValues ) {
sortKeysNumReturnValues = checkNumberOfReturnValues( numReturnValues );
sortKeysResult = new DoublyLinkedList( );
TSTNode startNode = getNode( prefix );
if ( startNode == null ) {
return sortKeysResult;
}
if ( startNode.data != null ) {
sortKeysResult.addLast( getKey( startNode ) );
sortKeysNumReturnValues--;
}
sortKeysList = true;
sortKeysRecursion( startNode.relatives [ TSTNode.EQKID ] );
return sortKeysResult;
}
/** Returns alphabetical list of all keys in the tree that begin with prefix. Only keys for nodes having non-null data
* are included in the list.
* @param prefix Each key returned from this method will begin with the characters in prefix.
* @return String A string representation of all keys matching the argument prefix. Keys are delimited with the newline
* char ('\n').
*/
public String matchPrefixString( String prefix ) {
return matchPrefixString( prefix, defaultNumReturnValues );
}
/** Returns alphabetical list of all keys in the tree that begin with prefix. Only keys for nodes having non-null data
* are included in the list.
* @param prefix Each key returned from this method will begin with the characters in prefix.
* @param numReturnValues The maximum number of values returned from this method.
* @return String A string representation of all keys matching the argument prefix. Keys are delimited with the newline
* char ('\n').
*/
public String matchPrefixString( String prefix, int numReturnValues ) {
TSTNode startNode = getNode( prefix );
if ( startNode == null ) {
return "";
}
sortKeysNumReturnValues = checkNumberOfReturnValues( numReturnValues );
lastNumberOfReturnValues = sortKeysNumReturnValues;
sortKeysBuffer = new StringBuffer( );
if ( startNode.data != null ) {
sortKeysBuffer.append( getKey( startNode ) + "\n" );
sortKeysNumReturnValues--;
}
sortKeysList = false;
sortKeysRecursion( startNode.relatives [ TSTNode.EQKID ] );
int bufferLength = sortKeysBuffer.length( );
if ( bufferLength > 0 ) {
sortKeysBuffer.setLength( bufferLength - 1 ); // delete the final \n
}
lastNumberOfReturnValues = lastNumberOfReturnValues - sortKeysNumReturnValues;
return sortKeysBuffer.toString( );
}
/* Returns keys sorted in alphabetical order. Includes currentNode and all nodes connected to currentNode. Sorted keys will
* be appended to end of result list. (result may be empty when this method is invoked, but may not be null.)
*/
private void sortKeysRecursion( TSTNode currentNode ) {
if ( currentNode == null ) {
return;
}
sortKeysRecursion( currentNode.relatives [ TSTNode.LOKID ] );
if ( sortKeysNumReturnValues == 0 ) {
return;
}
if ( currentNode.data != null ) {
if ( sortKeysList ) {
sortKeysResult.addLast( getKey( currentNode ) );
} else {
sortKeysBuffer.append( getKey( currentNode ) + "\n" );
}
sortKeysNumReturnValues--;
}
sortKeysRecursion( currentNode.relatives [ TSTNode.EQKID ] );
sortKeysRecursion( currentNode.relatives [ TSTNode.HIKID ] );
}
/** Returns keys sorted in alphabetical order. Includes startNode and all nodes connected to startNode.
* Number of keys returned is limited to numReturnValues. To get a list that isn't limited in size,
* set numReturnValues to -1.
* @param startNode The top node defining the subtree to be searched.
* @param numReturnValues The maximum number of values returned from this method.
* @return DoublyLinkedList An implementation of a LinkedList that is java 1.1 compatible.
*/
protected DoublyLinkedList sortKeys( TSTNode startNode, int numReturnValues ) {
sortKeysNumReturnValues = checkNumberOfReturnValues( numReturnValues );
sortKeysResult = new DoublyLinkedList( );
sortKeysRecursion( startNode );
return sortKeysResult;
}
public String sortKeysString( int numReturnValues ) {
return sortKeysString( rootNode, numReturnValues );
}
public String sortKeysString( ) {
return sortKeysString( rootNode, defaultNumReturnValues );
}
/** Returns keys sorted in alphabetical order, returning a result of type String. Includes startNode
* and all nodes connected to startNode. Number of keys returned is limited to numReturnValues. To get
* a list that isn't limited in size, set numReturnValues to -1.
* @param startNode The top node defining the subtree to be searched.
* @param numReturnValues The maximum number of values returned from this method.
* @return String A string representation of keys in alphabetical order.
*/
public String sortKeysString( TSTNode startNode, int numReturnValues ) {
if ( startNode == null ) {
return new String( "" );
}
sortKeysNumReturnValues = checkNumberOfReturnValues( numReturnValues );
lastNumberOfReturnValues = sortKeysNumReturnValues;
sortKeysBuffer = new StringBuffer( );
if ( startNode.data != null ) {
sortKeysBuffer.append( getKey( startNode ) + "\n" );
sortKeysNumReturnValues--;
}
sortKeysList = false;
sortKeysRecursion( startNode );
int bufferLength = sortKeysBuffer.length( );
if ( bufferLength > 0 ) {
sortKeysBuffer.setLength( bufferLength - 1 ); // delete the final \n
}
lastNumberOfReturnValues = lastNumberOfReturnValues - sortKeysNumReturnValues;
return sortKeysBuffer.toString( );
}
/** Returns a list of keys that almost match argument key.
* Keys returned will have exactly diff characters that do not match the target key,
* where diff is equal to the last value passed in as an argument to the setMatchAlmostDiff
* method. If the matchAlmost method is called before the setMatchAlmostDiff method has been
* called for the first time, then diff = 0.
* @param key The target key.
* @return DoublyLinkedList An implementation of a LinkedList that is java 1.1 compatible.
*/
public DoublyLinkedList matchAlmost( String key ) {
return matchAlmost( key, defaultNumReturnValues );
}
/** Returns a list of keys that almost match argument key.
* Keys returned will have exactly diff characters that do not match the target key,
* where diff is equal to the last value passed in as an argument to the setMatchAlmostDiff
* method. If the matchAlmost method is called before the setMatchAlmostDiff method has been
* called for the first time, then diff = 0.
* @param key The target key.
* @param numReturnValues The maximum number of values returned by this method.
* @return DoublyLinkedList An implementation of a LinkedList that is java 1.1 compatible.
*/
protected DoublyLinkedList matchAlmost( String key, int numReturnValues ) {
matchAlmostListAction = true;
matchAlmostNumReturnValues = checkNumberOfReturnValues( numReturnValues );
matchAlmostResult = new DoublyLinkedList( );
matchAlmostKey = key;
matchAlmostRecursion( rootNode, 0, matchAlmostDiff );
return matchAlmostResult;
}
/** Returns a String representation of keys that almost match argument key.
* Keys returned will have exactly diff characters that do not match the target key,
* where diff is equal to the last value passed in as an argument to the setMatchAlmostDiff
* method. If the matchAlmost method is called before the setMatchAlmostDiff method has been
* called for the first time, then diff = 0.
* @param key The target key.
* @return String A String representation of keys that almost match the target key. Keys are
* delimited by the newline char ('\n').
*/
public String matchAlmostString( String key ) {
return matchAlmostString( key, defaultNumReturnValues );
}
/** Returns a String representation of keys that almost match argument key.
* Keys returned will have exactly diff characters that do not match the target key,
* where diff is equal to the last value passed in as an argument to the setMatchAlmostDiff
* method. If the matchAlmost method is called before the setMatchAlmostDiff method has been
* called for the first time, then diff = 0.
* @param key The target key.
* @param numReturnValues The maximum number of values returned by this method.
* @return String A String representation of keys that almost match the target key. Keys are
* delimited by the newline char ('\n').
*/
protected String matchAlmostString( String key, int numReturnValues ) {
matchAlmostListAction = false;
matchAlmostNumReturnValues = checkNumberOfReturnValues( numReturnValues );
lastNumberOfReturnValues = matchAlmostNumReturnValues;
matchAlmostBuffer = new StringBuffer( );
matchAlmostKey = key;
matchAlmostRecursion( rootNode, 0, matchAlmostDiff );
int bufferLength = matchAlmostBuffer.length( );
if ( bufferLength > 0 ) {
matchAlmostBuffer.setLength( bufferLength - 1 ); // delete the final \n
}
lastNumberOfReturnValues = lastNumberOfReturnValues - matchAlmostNumReturnValues;
return matchAlmostBuffer.toString( );
}
private void matchAlmostRecursion( TSTNode currentNode, int charIndex, int d ) {
if ( ( currentNode == null ) || ( d < 0 ) || ( matchAlmostNumReturnValues == 0 ) || ( charIndex >= matchAlmostKey.length( ) ) ) {
return;
}
int charComp = CharUtility.compareCharsAlphabetically( matchAlmostKey.charAt( charIndex ), currentNode.splitchar );
// low branch
if ( ( d > 0 ) || ( charComp < 0 ) ) {
matchAlmostRecursion( currentNode.relatives [ TSTNode.LOKID ], charIndex, d );
}
//equal branch
int nextD = ( charComp == 0 ) ? d : ( d - 1 );
if ( ( matchAlmostKey.length( ) == ( charIndex + 1 ) ) && ( nextD == 0 ) && ( currentNode.data != null ) ) {
// Note: the condition nextD == 0 causes keys to be included in the result only if they have exactly matchAlmostDiff number
// of mismatched letters
// If instead the condition nextD >= 0 is used, then all keys having up to and including matchAlmostDiff mismatched letters
// will be included in the result (including a key that is exactly the same as the target string).
if ( matchAlmostListAction ) {
matchAlmostResult.addLast( getKey( currentNode ) );
} else {
matchAlmostBuffer.append( getKey( currentNode ) + "\n" );
}
matchAlmostNumReturnValues--;
}
matchAlmostRecursion( currentNode.relatives [ TSTNode.EQKID ], charIndex + 1, nextD );
// hi branch
if ( ( d > 0 ) || ( charComp > 0 ) ) {
matchAlmostRecursion( currentNode.relatives [ TSTNode.HIKID ], charIndex, d );
}
}
/** Sets the number of characters by which words can differ from target word when
* calling matchAlmost or matchAlmostString methods. Arguments
* less than 1 will set the char difference to 1, and arguments greater than 4
* will set the char difference to 4.
* @param diff The number of characters by which words can differ from target word.
*/
public void setMatchAlmostDiff( int diff ) {
if ( diff < 0 ) {
matchAlmostDiff = 0;
} else if ( diff > maxMatchAlmostDiff ) {
matchAlmostDiff = maxMatchAlmostDiff;
} else {
matchAlmostDiff = diff;
}
}
/** Returns the Node indexed by key, creating that node if it doesn't exist, and creating any required.
* intermediate nodes if they don't exist.
* @param key A string that indexes the node that is returned.
* @return TSTNode The node object indexed by key. This object is an instance of an inner class
* named TernarySearchTree.TSTNode.
*/
protected TSTNode getOrCreateNode( String key ) throws NullPointerException, IllegalArgumentException {
if ( key == null ) {
throw new NullPointerException( "attempt to get or create node with null key" );
}
if ( key.length( ) == 0 ) {
throw new IllegalArgumentException( "attempt to get or create node with key of zero length" );
}
if ( rootNode == null ) {
rootNode = new TSTNode( key.charAt( 0 ), null );
}
TSTNode currentNode = rootNode;
int charIndex = 0;
while ( true ) {
int charComp = CharUtility.compareCharsAlphabetically( key.charAt( charIndex ), currentNode.splitchar );
if ( charComp == 0 ) {
charIndex++;
if ( charIndex == key.length( ) ) {
return currentNode;
}
if ( currentNode.relatives [ TSTNode.EQKID ] == null ) {
currentNode.relatives [ TSTNode.EQKID ] = new TSTNode( key.charAt( charIndex ), currentNode );
}
currentNode = currentNode.relatives [ TSTNode.EQKID ];
} else if ( charComp < 0 ) {
if ( currentNode.relatives [ TSTNode.LOKID ] == null ) {
currentNode.relatives [ TSTNode.LOKID ] = new TSTNode( key.charAt( charIndex ), currentNode );
}
currentNode = currentNode.relatives [ TSTNode.LOKID ];
} else {
// charComp must be greater than zero
if ( currentNode.relatives [ TSTNode.HIKID ] == null ) {
currentNode.relatives [ TSTNode.HIKID ] = new TSTNode( key.charAt( charIndex ), currentNode );
}
currentNode = currentNode.relatives [ TSTNode.HIKID ];
}
}
}
/* Deletes the node passed in as an argument to this method. If this node has non-null data, then both the node and the data will be deleted.
* Also deletes any other nodes in the tree that are no longer needed after the deletion of the node first passed in as an argument to this method.
*/
private void deleteNode( TSTNode nodeToDelete ) {
if ( nodeToDelete == null ) {
return;
}
nodeToDelete.data = null;
while ( nodeToDelete != null ) {
nodeToDelete = deleteNodeRecursion( nodeToDelete );
}
}
/** Returns the key that indexes the node argument.
* @param node The node whose index is to be calculated.
* @return String The string that indexes the node argument.
*/
protected String getKey( TSTNode node ) {
getKeyBuffer.setLength( 0 );
getKeyBuffer.append( node.splitchar );
TSTNode currentNode;
TSTNode lastNode;
currentNode = node.relatives [ TSTNode.PARENT ];
lastNode = node;
while ( currentNode != null ) {
if ( currentNode.relatives [ TSTNode.EQKID ] == lastNode ) {
getKeyBuffer.append( currentNode.splitchar );
}
lastNode = currentNode;
currentNode = currentNode.relatives [ TSTNode.PARENT ];
}
getKeyBuffer.reverse( );
return getKeyBuffer.toString( );
}
/** Returns the total number of nodes in the tree. Counts nodes whether or not they have data.
* @return int The total number of nodes in the tree.
*/
public int numNodes( ) {
return numNodes( rootNode );
}
/** Returns the total number of nodes in the subtree below and including startingNode. Counts nodes whether or not they have data.
* @param startingNode The top node of the subtree. The node that defines the subtree.
* @return int The total number of nodes in the subtree.
*/
protected int numNodes( TSTNode startingNode ) {
numNodes = 0;
checkData = false;
recursiveNodeCalculator( startingNode );
return numNodes;
}
/** Returns the number of nodes in the tree that have non-null data.
* @return int The number of nodes in the tree that have non-null data.
*/
public int numDataNodes( ) {
return numDataNodes( rootNode );
}
/** Returns the number of nodes in the subtree below and including startingNode. Counts only nodes that have non-null data.
* @param startingNode The top node of the subtree. The node that defines the subtree.
* @return int The total number of nodes in the subtree.
*/
protected int numDataNodes( TSTNode startingNode ) {
numNodes = 0;
checkData = true;
recursiveNodeCalculator( startingNode );
return numNodes;
}
private void recursiveNodeCalculator( TSTNode currentNode ) {
if ( currentNode == null ) {
return;
}
recursiveNodeCalculator( currentNode.relatives [ TSTNode.LOKID ] );
recursiveNodeCalculator( currentNode.relatives [ TSTNode.EQKID ] );
recursiveNodeCalculator( currentNode.relatives [ TSTNode.HIKID ] );
if ( checkData ) {
if ( currentNode.data != null ) {
numNodes++;
}
} else {
numNodes++;
}
}
public void deleteTree( ) {
if ( rootNode == null ) {
return;
}
// Simply detach the lot and let gc deal with it
rootNode.relatives = new TSTNode[ 4 ];
rootNode.data = null;
System.gc( );
}
/** Prints entire tree structure to standard output, beginning with the root node and workind down.
*/
protected void printTree( ) {
System.out.println( "" );
if ( rootNode == null ) {
System.out.println( "tree is empty" );
return;
}
System.out.println( "Here's the entire tree structure:" );
printNodeRecursion( rootNode );
}
/** Prints subtree structure to standard output, beginning with startingNode and workind down.
*/
protected void printTree( TSTNode startingNode ) {
System.out.println( "" );
if ( rootNode == null ) {
System.out.println( "subtree is empty" );
return;
}
System.out.println( "Here's the entire subtree structure:" );
printNodeRecursion( startingNode );
}
/** Recursive method used to print out tree or subtree structure.
*/
private void printNodeRecursion( TSTNode currentNode ) {
if ( currentNode == null ) {
return;
}
System.out.println( "" );
System.out.println( "( keys are delimited by vertical lines: |example key| )" );
System.out.println( "info for node |" + getKey( currentNode ) + "| node data: " + currentNode.data );
if ( currentNode.relatives [ TSTNode.PARENT ] == null ) {
System.out.println( "parent null" );
} else {
System.out.println( "parent key |" + getKey( currentNode.relatives [ TSTNode.PARENT ] ) + "| parent data: " +
currentNode.relatives [ TSTNode.PARENT ].data );
}
if ( currentNode.relatives [ TSTNode.LOKID ] == null ) {
System.out.println( "lokid null" );
} else {
System.out.println( "lokid key |" + getKey( currentNode.relatives [ TSTNode.LOKID ] ) + "| lo kid data: " +
currentNode.relatives [ TSTNode.LOKID ].data );
}
if ( currentNode.relatives [ TSTNode.EQKID ] == null ) {
System.out.println( "eqkid null" );
} else {
System.out.println( "eqkid key |" + getKey( currentNode.relatives [ TSTNode.EQKID ] ) + "| equal kid data: " +
currentNode.relatives [ TSTNode.EQKID ].data );
}
if ( currentNode.relatives [ TSTNode.HIKID ] == null ) {
System.out.println( "hikid null" );
} else {
System.out.println( "hikid key |" + getKey( currentNode.relatives [ TSTNode.HIKID ] ) + "| hi kid data: " +
currentNode.relatives [ TSTNode.HIKID ].data );
}
printNodeRecursion( currentNode.relatives [ TSTNode.LOKID ] );
printNodeRecursion( currentNode.relatives [ TSTNode.EQKID ] );
printNodeRecursion( currentNode.relatives [ TSTNode.HIKID ] );
}
// Fix to stop Exception while deleting the currentParent node.
private TSTNode deleteNodeRecursion( TSTNode currentNode ) {
if ( currentNode == null ) {
return null;
}
if ( ( currentNode.relatives [ TSTNode.EQKID ] != null ) || ( currentNode.data != null ) ) {
return null;
}
TSTNode currentParent = currentNode.relatives [ TSTNode.PARENT ];
boolean lokidNull = currentNode.relatives [ TSTNode.LOKID ] == null;
boolean hikidNull = currentNode.relatives [ TSTNode.HIKID ] == null;
int childType;
if ( currentParent == null ) {
rootNode = null;
return null;
}
if ( currentParent.relatives [ TSTNode.LOKID ] == currentNode ) {
childType = TSTNode.LOKID;
} else if ( currentParent.relatives [ TSTNode.EQKID ] == currentNode ) {
childType = TSTNode.EQKID;
} else {
childType = TSTNode.HIKID;
}
if ( lokidNull && hikidNull ) {
currentParent.relatives [ childType ] = null;
return currentParent;
}
if ( lokidNull ) {
currentParent.relatives [ childType ] = currentNode.relatives [ TSTNode.HIKID ];
currentNode.relatives [ TSTNode.HIKID ].relatives [ TSTNode.PARENT ] = currentParent;
return currentParent;
}
if ( hikidNull ) {
currentParent.relatives [ childType ] = currentNode.relatives [ TSTNode.LOKID ];
currentNode.relatives [ TSTNode.LOKID ].relatives [ TSTNode.PARENT ] = currentParent;
return currentParent;
}
int deltaHi = currentNode.relatives [ TSTNode.HIKID ].splitchar - currentNode.splitchar;
int deltaLo = currentNode.splitchar - currentNode.relatives [ TSTNode.LOKID ].splitchar;
int movingKid;
TSTNode targetNode;
if ( deltaHi == deltaLo ) {
if ( Math.random( ) < 0.5 ) {
deltaHi++;
} else {
deltaLo++;
}
}
if ( deltaHi > deltaLo ) {
movingKid = TSTNode.HIKID;
targetNode = currentNode.relatives [ TSTNode.LOKID ];
} else {
movingKid = TSTNode.LOKID;
targetNode = currentNode.relatives [ TSTNode.HIKID ];
}
while ( targetNode.relatives [ movingKid ] != null ) {
targetNode = targetNode.relatives [ movingKid ];
}
targetNode.relatives [ movingKid ] = currentNode.relatives [ movingKid ];
currentParent.relatives [ childType ] = targetNode;
targetNode.relatives [ TSTNode.PARENT ] = currentParent;
if ( !lokidNull ) {
currentNode.relatives [ TSTNode.LOKID ] = null;
}
if ( !hikidNull ) {
currentNode.relatives [ TSTNode.HIKID ] = null;
}
return currentParent;
}
/** An inner class of TernarySearchTree that represents a node in the tree.
*/
protected class TSTNode {
protected static final int PARENT = 0; // index values for accessing relatives array
protected static final int LOKID = 1; // index values for accessing relatives array
protected static final int EQKID = 2; // index values for accessing relatives array
protected static final int HIKID = 3; // index values for accessing relatives array
protected char splitchar;
protected TSTNode[] relatives = new TSTNode[ 4 ];
protected Object data;
protected TSTNode( char splitchar, TSTNode parent ) {
this.splitchar = splitchar;
relatives [ PARENT ] = parent;
}
}
}