/* * 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; import net.jxta.logging.Logger; import net.jxta.logging.Logging; import java.util.ArrayList; import java.util.List; /** * * 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. * * Credits: * * Modified by Bruno Grieder: bruno.grieder@amalto.com * Improvements to the Match Prefix with Threshold recursive algorithms * Minor speed improvements * * Modified by Simon Temple: simon.temple@amalto.com * Added interface * BUG FIX: NPE on final node removal (left as comment to WEB article; contributor unknown). * BUG FIX: Re-coded delete of nodes with both LO and HI KID relatives * BUG FIX: Assign new parent KID to node following move due to delete * Removed ASCII comparison in favour of using java Character class * Added maxNodeTraversal warnings * * Modified by Yan Cheng for generic introduction and thread safe matchPrefix. * * Original Author Wally Flint: wally@wallyflint.com * * With thanks to Michael Amster of webeasy.com for introducing Wally Flint to * the Ternary Search Tree, and providing some starting code. * */ public class TernarySearchTreeImpl<E> implements TernarySearchTree<E> { private final static long MAX_NODE_TRAVERSALS = Long.getLong( TernarySearchTreeImpl.class.getName( ) + ".maxNodeTraversals", 300 ); private final static Logger LOG = Logging.getLogger( TernarySearchTreeImpl.class.getName( ) ); private volatile static int treesCreated = 0; private volatile TSTNode<E> rootNode = null; // Convenience variable for getKey method - not synchronised but faster than StringBuffer() private StringBuilder getKeyBuffer = new StringBuilder( ); // The Tree Name, if any private volatile String treeName; public TernarySearchTreeImpl( ) { this( null ); } public TernarySearchTreeImpl( String name ) { this.treeName = String.valueOf( ++treesCreated ) + "-" + ( ( name == null ) ? String.valueOf( hashCode( ) ) : name ); } /* (non-Javadoc) * @see net.jxta.impl.util.ternary.TernarySearchTree#deleteTree() */ @SuppressWarnings( "unchecked" ) public void deleteTree( ) { if ( rootNode != null ) { // Simply detach the lot and let gc deal with it rootNode.relatives = new TSTNode[ 4 ]; rootNode.data = null; } } /* (non-Javadoc) * @see net.jxta.impl.util.ternary.TernarySearchTree#getOrCreate(java.lang.String, E) */ public E getOrCreate( final String key, final E valueIfCreate ) { TSTNode<E> node = getOrCreateNode( key ); if ( node.data == null ) { node.data = valueIfCreate; } return node.data; } /* (non-Javadoc) * @see net.jxta.impl.util.ternary.TernarySearchTree#put(java.lang.String, E) */ public void put( final String key, final E value ) { getOrCreateNode( key ).data = value; } /* (non-Javadoc) * @see net.jxta.impl.util.ternary.TernarySearchTree#get(java.lang.String) */ public E get( final String key ) { TSTNode<E> node = getNode( key ); if ( node == null ) { return null; } return node.data; } /* (non-Javadoc) * @see net.jxta.impl.util.ternary.TernarySearchTree#remove(java.lang.String) */ public E remove( final String key ) { TSTNode<E> node = getNode( key ); E nodeData = null; if ( null != node ) { nodeData = node.data; deleteNode( node ); } else { if ( Logging.SHOW_WARNING && LOG.isWarnEnabled() ) { LOG.warn( ( ( treeName == null ) ? "" : ( "[" + treeName + "] " ) ) + "Failed to find node to remove given key: " + key ); } } return nodeData; } /** * 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<E> getNode( final 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<E> getNode( final String key, final TSTNode<E> startNode ) { if ( ( key == null ) || ( startNode == null ) || ( key.length( ) == 0 ) ) { return null; } TSTNode<E> currentNode = startNode; int charIndex = 0; int nodesTraversed = 0; while ( true ) { if ( currentNode == null ) { return null; } int charComp = 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 = (TSTNode<E>) currentNode.relatives [ TSTNode.HIKID ]; } if ( ++nodesTraversed > MAX_NODE_TRAVERSALS ) { nodesTraversed = 0; if ( Logging.SHOW_WARNING && LOG.isWarnEnabled() ) { LOG.warn( ( ( treeName == null ) ? "" : ( "[" + treeName + "] " ) ) + "Excessive node traversal detected. Tree is either broken or very inefficient!" ); } } } } /* (non-Javadoc) * @see net.jxta.impl.util.ternary.TernarySearchTree#matchPrefix(java.lang.String) */ public List<E> matchPrefix( final String prefix ) { return matchPrefix( prefix, null ); } /* (non-Javadoc) * @see net.jxta.impl.util.ternary.TernarySearchTree#matchPrefix(java.lang.String, int) */ public List<E> matchPrefix( final String prefix, final TernarySearchTreeMatchListener<E> listener ) { // The returned list List<E> sortKeysResult = new ArrayList<E>( ); // No one should query with a listener set to stop search, but who knows.... if ( ( listener != null ) && !listener.continueSearch( ) ) { return sortKeysResult; } // Find the highest node matching the prefix TSTNode<E> startNode = getNode( prefix ); // No match -> the prefix does not exist in the tree if ( startNode == null ) { return sortKeysResult; } // If we have data a this level (e.g. node exactly matching the prefix), collect it if ( startNode.data != null ) { sortKeysResult.add( startNode.data ); if ( listener != null ) { listener.resultFound( prefix, startNode.data ); if ( !listener.continueSearch( ) ) { return sortKeysResult; } } } // Start going down the tree to collect additional results sortKeysRecursion( sortKeysResult, startNode.relatives [ TSTNode.EQKID ], prefix, listener ); return sortKeysResult; } private void sortKeysRecursion( final List<E> sortKeysResult, final TSTNode<E> currentNode, final String currentKey, final TernarySearchTreeMatchListener<E> listener ) { // We have gone to the left tip of this branch --> move back if ( currentNode == null ) { return; } // If we have data available at that node, collect it and keep going left if ( currentNode.data != null ) { sortKeysResult.add( currentNode.data ); if ( listener != null ) { listener.resultFound( currentKey + currentNode.splitchar, currentNode.data ); // Check if listener calls for end the search if ( !listener.continueSearch( ) ) { return; } } } // Keep going left sortKeysRecursion( sortKeysResult, currentNode.relatives [ TSTNode.LOKID ], currentKey, listener ); // We have done the left branch, start the middle branch sortKeysRecursion( sortKeysResult, currentNode.relatives [ TSTNode.EQKID ], currentKey + currentNode.splitchar, listener ); // Finally, run the right branch sortKeysRecursion( sortKeysResult, currentNode.relatives [ TSTNode.HIKID ], currentKey, listener ); } /** * 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<E> getOrCreateNode( final String key ) throws NullPointerException, IllegalArgumentException { if ( key == null ) { throw new NullPointerException( ( ( treeName == null ) ? "" : ( "[" + treeName + "] " ) ) + "Attempt to get or create node with null key" ); } if ( key.length( ) == 0 ) { throw new IllegalArgumentException( ( ( treeName == null ) ? "" : ( "[" + treeName + "] " ) ) + "Attempt to get or create node with key of zero length" ); } if ( rootNode == null ) { rootNode = new TSTNode<E>( key.charAt( 0 ), null ); } TSTNode<E> currentNode = rootNode; int charIndex = 0; int nodesTraversed = 0; while ( true ) { char currentChar = key.charAt( charIndex ); int charComp = compareCharsAlphabetically( currentChar, currentNode.splitchar ); if ( charComp == 0 ) { charIndex++; if ( charIndex == key.length( ) ) { return currentNode; } if ( currentNode.relatives [ TSTNode.EQKID ] == null ) { currentNode.relatives [ TSTNode.EQKID ] = new TSTNode<E>( 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<E>( currentChar, 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<E>( currentChar, currentNode ); } currentNode = currentNode.relatives [ TSTNode.HIKID ]; } if ( ++nodesTraversed > MAX_NODE_TRAVERSALS ) { nodesTraversed = 0; if ( Logging.SHOW_WARNING && LOG.isWarnEnabled() ) { LOG.warn( ( ( treeName == null ) ? "" : ( "[" + treeName + "] " ) ) + "Excessive node traversal detected. Tree is either broken or very inefficient!" ); } } } } /* * 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( final TSTNode<E> nodeToDelete ) { if ( nodeToDelete == null ) { return; } TSTNode<E> node = nodeToDelete; node.data = null; while ( node != null ) { // LOGGING: was Finest if ( Logging.SHOW_DEBUG && LOG.isDebugEnabled() ) { LOG.debug( "Deleting tree node: " + node ); } node = deleteNodeRecursion( node ); } } private TSTNode<E> deleteNodeRecursion( final TSTNode<E> currentNode ) { // To delete a node, first set its data to null, then pass it into this method, then pass the node returned by this method into this method // (make sure you don't delete the data of any of the nodes returned from this method!) // and continue in this fashion until the node returned by this method is null. // The TSTNode instance returned by this method will be next node to be operated on by deleteNodeRecursion. // (This emulates recursive method call while avoiding the JVM overhead normally associated with a recursive method.) if ( currentNode == null ) { return null; } if ( ( currentNode.relatives [ TSTNode.EQKID ] != null ) || ( currentNode.data != null ) ) { return null; // Can't delete this node if it has a non-null eq kid or data } TSTNode<E> currentParent = currentNode.relatives [ TSTNode.PARENT ]; // If we've made it this far, then we know the currentNode isn't null, but its data and equal kid are null, so we can delete the current node // (before deleting the current node, we'll move any lower nodes higher in the tree) boolean lokidNull = currentNode.relatives [ TSTNode.LOKID ] == null; boolean hikidNull = currentNode.relatives [ TSTNode.HIKID ] == null; // Now find out what kind of child current node is int childType; if ( currentParent != null ) { if ( currentParent.relatives [ TSTNode.LOKID ] == currentNode ) { childType = TSTNode.LOKID; } else if ( currentParent.relatives [ TSTNode.EQKID ] == currentNode ) { childType = TSTNode.EQKID; } else if ( currentParent.relatives [ TSTNode.HIKID ] == currentNode ) { childType = TSTNode.HIKID; } else { // If this executes, then current node is root node return null; } if ( lokidNull && hikidNull ) { // If we make it to here, all three kids are null and we can just delete this node currentParent.relatives [ childType ] = null; return currentParent; } // If we make it this far, we know that EQKID is null, and either HIKID or LOKID is null, or both HIKID and LOKID are NON-null 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<E> targetNode; // If deltaHi is equal to deltaLo, then choose one of them at random, and make it "further away" from the current node's splitchar 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 ]; } // Now targetNode.relatives[movingKid] is null, and we can put the moving kid into it. targetNode.relatives [ movingKid ] = currentNode.relatives [ movingKid ]; // Assign the new parent currentNode.relatives [ movingKid ].relatives [ TSTNode.PARENT ] = targetNode; // Now we need to put the target node where the current node used to be currentParent.relatives [ childType ] = targetNode; targetNode.relatives [ TSTNode.PARENT ] = currentParent; if ( !lokidNull ) { if ( ( movingKid != TSTNode.LOKID ) && ( null != currentNode.relatives [ TSTNode.LOKID ] ) && ( targetNode != currentNode.relatives [ TSTNode.LOKID ] ) ) { // We must re-graft the LOKID if it exists while ( targetNode.relatives [ TSTNode.LOKID ] != null ) { targetNode = targetNode.relatives [ TSTNode.LOKID ]; } // Null the old child reference currentNode.relatives [ TSTNode.LOKID ].relatives [ TSTNode.HIKID ] = null; // and re-graft... targetNode.relatives [ TSTNode.LOKID ] = currentNode.relatives [ TSTNode.LOKID ]; currentNode.relatives [ TSTNode.LOKID ].relatives [ TSTNode.PARENT ] = targetNode; } currentNode.relatives [ TSTNode.LOKID ] = null; } if ( !hikidNull ) { if ( ( movingKid != TSTNode.HIKID ) && ( null != currentNode.relatives [ TSTNode.HIKID ] ) && ( targetNode != currentNode.relatives [ TSTNode.HIKID ] ) ) { // We must re-graft the HIKID if it exists while ( targetNode.relatives [ TSTNode.HIKID ] != null ) { targetNode = targetNode.relatives [ TSTNode.HIKID ]; } // Null the old child reference currentNode.relatives [ TSTNode.HIKID ].relatives [ TSTNode.LOKID ] = null; // and re-graft... targetNode.relatives [ TSTNode.HIKID ] = currentNode.relatives [ TSTNode.HIKID ]; currentNode.relatives [ TSTNode.HIKID ].relatives [ TSTNode.PARENT ] = targetNode; } currentNode.relatives [ TSTNode.HIKID ] = null; } currentNode.relatives [ TSTNode.PARENT ] = null; } // Note that the statements above ensure currentNode is completely dereferenced, and so it will be garbage collected return currentParent; } private static int compareCharsAlphabetically( final char cCompare, final Character charRef ) { // Use java Character class comparison and not some half baked ASCII char comparison Character chr1 = Character.valueOf( cCompare ); return ( chr1.compareTo( charRef ) ); } // Prints entire tree structure to standard output, beginning with the root node and working down. public void printTree( ) { System.out.println( "" ); if ( rootNode == null ) { System.out.println( "Tree is empty!\n" ); return; } System.out.println( "Note: keys are delimited by vertical lines: |example key|\n" ); printNodeRecursion( rootNode ); } // Prints subtree structure to standard output, beginning with startingNode and working down. protected void printTree( TSTNode<E> startingNode ) { System.out.println( "" ); if ( rootNode == null ) { System.out.println( "Subtree is empty!" ); return; } System.out.println( "Note: keys are delimited by vertical lines: |example key|\n" ); printNodeRecursion( startingNode ); } public void walkTree( final TernarySearchTreeMatchListener<E> listener ) { walkNodeRecursion( rootNode, listener ); } private void walkNodeRecursion( TSTNode<E> currentNode, final TernarySearchTreeMatchListener<E> listener ) { if ( currentNode == null ) { return; } if ( null != currentNode.data ) { listener.resultFound( getKey( currentNode ), currentNode.data ); } walkNodeRecursion( currentNode.relatives [ TSTNode.LOKID ], listener ); walkNodeRecursion( currentNode.relatives [ TSTNode.EQKID ], listener ); walkNodeRecursion( currentNode.relatives [ TSTNode.HIKID ], listener ); } // Recursive method used to print out tree or subtree structure. private void printNodeRecursion( TSTNode<E> currentNode ) { if ( currentNode == null ) { return; } System.out.println( "" ); System.out.println( "--------------------------------------------------------------------------------" ); System.out.println( "info for node\t|" + getKey( currentNode ) + "|\tnode data:\t" + currentNode.data + "\n" ); if ( currentNode.relatives [ TSTNode.PARENT ] == null ) { System.out.println( "parent\t\tnull" ); } else { System.out.println( "parent key\t|" + getKey( currentNode.relatives [ TSTNode.PARENT ] ) + "|\tparent data:\t" + currentNode.relatives [ TSTNode.PARENT ].data ); } if ( currentNode.relatives [ TSTNode.LOKID ] == null ) { System.out.println( "lokid\t\tnull" ); } else { System.out.println( "lokid key\t|" + getKey( currentNode.relatives [ TSTNode.LOKID ] ) + "|\tlo kid data:\t" + currentNode.relatives [ TSTNode.LOKID ].data ); } if ( currentNode.relatives [ TSTNode.EQKID ] == null ) { System.out.println( "eqkid\t\tnull" ); } else { System.out.println( "eqkid key\t|" + getKey( currentNode.relatives [ TSTNode.EQKID ] ) + "|\tequal kid data:\t" + currentNode.relatives [ TSTNode.EQKID ].data ); } if ( currentNode.relatives [ TSTNode.HIKID ] == null ) { System.out.println( "hikid\t\tnull" ); } else { System.out.println( "hikid key\t|" + getKey( currentNode.relatives [ TSTNode.HIKID ] ) + "|\thi kid data:\t" + currentNode.relatives [ TSTNode.HIKID ].data ); } System.out.println( "--------------------------------------------------------------------------------" ); printNodeRecursion( currentNode.relatives [ TSTNode.LOKID ] ); printNodeRecursion( currentNode.relatives [ TSTNode.EQKID ] ); printNodeRecursion( currentNode.relatives [ TSTNode.HIKID ] ); } /** 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<E> node ) { getKeyBuffer.setLength( 0 ); getKeyBuffer.append( node.splitchar ); TSTNode<E> currentNode; TSTNode<E> 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( ); } /** * An inner class of TernarySearchTree that represents a node in the tree. */ private static final class TSTNode<E> { // Index values for accessing relatives array protected static final int PARENT = 0; protected static final int LOKID = 1; protected static final int EQKID = 2; protected static final int HIKID = 3; // Node fields...all volatile protected volatile Character splitchar; @SuppressWarnings( "unchecked" ) protected volatile TSTNode<E>[] relatives = new TSTNode[ 4 ]; protected volatile E data; protected TSTNode( char splitchar, TSTNode<E> parent ) { this.splitchar = splitchar; relatives [ PARENT ] = parent; } public String toString( ) { return "Node(" + splitchar + "):: Numeric:" + Character.getNumericValue( splitchar ); } } }