key0-96/
key0-96/doc/key/
key0-96/doc/key/credits/
key0-96/doc/key/developers/
key0-96/doc/key/developers/resources/
key0-96/setup/caves/
key0-96/setup/help/
key0-96/setup/ruins/
key0-96/src/
key0-96/src/commands/
key0-96/src/events/
key0-96/src/hack/
key0-96/src/sql/
key0-96/src/swing/
key0-96/src/talker/forest/
key0-96/src/talker/objects/
key0-96/src/terminals/
/*
**               j###t  ########## ####   ####
**              j###t   ########## ####   ####
**             j###T               "###L J###"
**          ######P'    ##########  #########
**          ######k,    ##########   T######T
**          ####~###L   ####
**          #### q###L  ##########   .#####
**          ####  \###L ##########   #####"
**
**  $Id$
**
**  Class History
**
**  Date        Name         Description
**  ---------|------------|-----------------------------------------------
**  21Feb98     subtle       created
**
*/

package key;

import java.util.StringTokenizer;

/**
  *  This class is used to contain search parameters for the routines
  *  which iterate through a string to produce a symbol.  For instance,
  *  while matching '/players/subtle.name', a Search object will be
  *  created for the duration of the search, to hold transient information
  *  required for abstraction purposes.
  *
  * @see key.Searchable
 */
public final class Search
{
	public StringTokenizer st;
	
	/**
	  *  Holds the last atom we passed through.  This is
	  *  necessary in case we wish to return an AtomicElement
	  *  from the search, in which case the atom that the
	  *  element is on is very important if we wish to get
	  *  or set the actual value.
	 */
	public Atom lastAtom;
	
	/**
	  *  holds the final result of our search, properties & transient
	  *  stepped through
	 */
	public Object result;
	
	/**
	  *  holds the first matched result of our search, useful for set() routines
	  *  that need to access the low-level provider.  specifically, this is
	  *  the object before it has been notified that it is the end result
	  *  of a search
	 */
	public Object matchedResult;
	
	public boolean dirty;
	public boolean resolveReference = true;
	public boolean resolve = true;
	
	public Search( String s, Atom relativeTo )
	{
		doSearch( s, relativeTo );
	}
	
	public Search( String s, Atom relativeTo, boolean resolveReferences, boolean resolve )
	{
		this.resolveReference = resolveReferences;
		this.resolve = resolve;
		doSearch( s, relativeTo );
	}
	
	public void doSearch( String s, Atom relativeTo ) throws InvalidSearchException
	{
		int count = 0;
		int len = s.length();
		
		if( len <= 0 )
		{
			result = relativeTo;
			st = new StringTokenizer( s, "./@", true );
		}
		else
		{
			char c = s.charAt( 0 );
			Object oldResult;
			
			switch( c )
			{
				case '/':
					if( len > 1 && s.charAt( 1 ) == '.' )
						s = s.substring( 1 );
					else
						;
						//  don't strip the leading slash, it's
						//  needed to show that we want an element,
						//  not a property
					
					relativeTo = Key.instance();
					break;
				case '~':
					s = s.substring( 1 );
					relativeTo = Key.shortcuts();
					break;
				case '#':
					s = s.substring( 1 );
					
					String number = null;
					
						//  try to strip the number off the front
					for( int i = 0; i < s.length(); i++ )
					{
						if( !Character.isDigit( s.charAt( i ) ) )
						{
							number = s.substring( 0, i );
							s = s.substring( i );
							break;
						}
					}
					
					if( number == null )
					{
						number = s;
						s = "";
					}
					
					int index = 0;
					
					try
					{
						index = Integer.parseInt( number );
					}
					catch( NumberFormatException e )
					{
						throw new InvalidSearchException( e.toString() + " while matching id #" );
					}
					
					relativeTo = Registry.instance.get( index );
					
					break;
			}
			
			st = new StringTokenizer( s, "./@", true );
			lastAtom = relativeTo;
			result = relativeTo;
			
			while( result instanceof Searchable && st.hasMoreTokens() && count < 20 )
			{
				((Searchable)result).search( this );
				
				count++;
			}
			
			if( count >= 20 )
				throw new InvalidSearchException( "search path too deep (> 20)" );
		}
		
		Object lastResult = null;
		count = 0;
		
		matchedResult = result;
		
			//
			//  one last time notifies result of our intention
			//  to use it as the search results.  this permits transient
			//  matches to resolve, or offline atoms with stubs to be
			//  loaded and returned.
			//
			//  if one of these atoms returns a different result, we need
			//  to notify that different result, as well, hence the while
			//
			//  again, we use 'count' to bound the limit of these to 20, just
			//  to stop circular searches
			//
		if( resolve )
		{
			if( !resolveReference )
			{
				while
				(
					(result instanceof Searchable) &&
					!(
						(result instanceof Reference) &&
						!(((Reference)result).isLoaded())
					) &&
					(lastResult != result) &&
					(count < 20)
				)
				{
					lastResult = result;
					((Searchable)result).search( this );
					count++;
				}
			}
			else
			{
				while( (result instanceof Searchable) && (lastResult != result) && (count < 20) )
				{
					lastResult = result;
					((Searchable)result).search( this );
					count++;
				}
			}
			
			if( count >= 20 )
				throw new InvalidSearchException( "search resolve path too deep (> 20)" );
		}
	}
}