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
**  ---------|------------|-----------------------------------------------
**  19Aug98     subtle       start of recorded history
**
*/

package key;

import key.util.SeperatedIdentifier;

import java.io.*;
import java.util.StringTokenizer;

/**
  *  A reference is an immutable pointer to an object, stored in a
  *  manner that allows it to be maintained through swaps of the
  *  referenced atom back to disk, as well as system resets.
 */
public abstract class Reference
	implements Serializable, Searchable, Symbol, Cloneable, 
	           key.io.Replaceable
{
	private static final long serialVersionUID = -1279870814310783156L;
	public static final Reference to( Atom a )
	{
		return( new NamedDirectReference( a ) );
	}
	
	public Object clone()
	{
		try
		{
			return( super.clone() );
		}
		catch( CloneNotSupportedException e )
		{
			throw new UnexpectedResult( e.toString() + " in reference::clone()" );
		}
	}
	
	public static final Reference to( String id, Atom context )
	{
		if( id.equals( "$" ) )
			return( Reference.EMPTY );
		
		Search s = new Search( id, context );
		
		if( s.dirty || s.result == null )
			return( new IndirectReference( id ) );
		else
			return( ((Atom)s.result).getThis() );
	}
	
	public static final Reference to( String id )
	{
		if( id.equals( "$" ) )
			return( Reference.EMPTY );
		
		Search s = new Search( id, Key.instance() );
		
		if( s.dirty || s.result == null )
			return( new IndirectReference( id ) );
		else
			return( ((Atom)s.result).getThis() );
	}
	
	public static final Reference to( Atom a, boolean named )
	{
		if( named )
			return( new NamedDirectReference( a ) );
		else
			return( new DirectReference( a ) );
	}
	
	/**
	  *  @param id the full path (leading with a '/' or '~') of the reference
	  *  @param named true if the name of the atom should be stored in the
	  *               reference
	  *  @param noDirect true if a indirect reference is required - no lookup
	  *                  translation should be performed.  This is useful if
	  *                  a property can change or move and we still wish to
	  *                  point to it.  This is the equivalent of a unix softlink.
	 */
	public static final Reference to( String id, boolean named, boolean noDirect )
	{
		if( id.equals( "$" ) )
			return( Reference.EMPTY );
		
		if( noDirect )
			return( new IndirectReference( id ) );
		
		Search s = new Search( id, Key.instance() );
		
		if( s.dirty || (s.result == null) )
			return( new IndirectReference( id ) );
		else
			return( new DirectReference( (Atom)s.result ) );
	}
	
	public abstract Atom get();
	
	public abstract Atom getType( Class c );
	
	/**
	  * @return true iff these two references point to the same
	  *         atom.
	 */
	public abstract boolean equals( Object o );
	
	public int hashCode()
	{
		return( get().hashCode() );
	}
	
	/**
	  * @return true iff this reference points to this atom
	 */
	public abstract boolean contains( Atom a );
	
	/**
	  * @return false if this atom is definately not in memory.  May
	  *         return true if the atom is not in memory, particularly
	  *         if this is a symbol reference.  Use this routine as a
	  *         guide, not as a definitive result.  should work for
	  *         getThis() references, though.
	 */
	public abstract boolean isLoaded();

	public int getIndex()
	{
		return( get().index );
	}

	public int getTimestamp()
	{
		return( get().timestamp );
	}

	public String getName()
	{
		return( "" );
	}

	public void search( Search s ) throws InvalidSearchException
	{
		s.result = get();
	}
	
	public Object getKey()
	{
		return( this );
	}
	
	public void setKey( Object k )
	{
		throw new UnexpectedResult( "setKey::called on Reference" );
	}
	
	/**
	  *  Return false if this reference can be replaced by EMPTY
	 */
	public boolean isValid()
	{
		return( true );
	}
	
	public Object getReplacement()
	{
		return( this );
	}
	
	public static final EmptyReference EMPTY = new EmptyReference();
}

class EmptyReference
	extends Reference
{
	private static final long serialVersionUID = -5345429757003014274L;
	
	public EmptyReference()
	{
	}
	
	public Object getReplacement()
	{
		return( Reference.EMPTY );
	}
	
	public Object clone()
	{
		return( Reference.EMPTY );
	}
	
	public int hashCode()
	{
		return( 0 );
	}
	
	public boolean isLoaded()
	{
		return false;
	}

	public boolean equals( Object o )
	{
		return false;
	}

	public boolean contains( Atom a )
	{
		return false;
	}

	public Atom get()
	{
		return( null );
	}

	public Atom getType( Class c )
	{
		return( null );
	}
	
	public boolean isValid()
	{
		return( false );
	}
}

/**
  *  A reference that is stored with a string id that is looked up
  *  at each get() operation.
  *
  *  This is useful for references that need to travel through transit
  *  atoms (references that need to change).
 */
class IndirectReference
	extends Reference
	implements Symbol
{
	String id;
	
	public IndirectReference( String path )
	{
		id = path;
	}
	
	public Atom get()
	{
		Search s = new Search( id, Key.instance() );
		
		return( (Atom) s.result );
	}
	
	public Atom getType( Class c )
	{
		Atom a = get();

		if( a == null || c.isInstance( a ) )
			return( a );
		else
			return( null );
	}
	
	public int hashCode()
	{
		return( id.hashCode() );
	}
	
	public Object getKey()
	{
		return( new SeperatedIdentifier( id ).id );
	}
	
	public String getName()
	{
		return( (String) getKey() );
	}
	
	public void setKey( Object k )
	{
		SeperatedIdentifier si = new SeperatedIdentifier( id );
		
		StringBuffer sb = new StringBuffer( si.location );
		
		if( si.property )
			sb.append( '.' );
		else
			sb.append( '/' );
		
		sb.append( k.toString() );
		
		id = sb.toString();
	}

	public boolean equals( Object o )
	{
		if( o instanceof IndirectReference )
			return( id.equalsIgnoreCase( ((IndirectReference)o).id ) );
		else if( o instanceof Reference )
			return( get().equals( ((Reference)o).get() ) );
		else
			return( false );
	}
	
	public boolean contains( Atom a )
	{
		return( get() == a );
	}

	public boolean isLoaded()
	{
		return( true );
	}
}

/**
  *  A reference that stores the integer index of this atom in the
  *  system registry.  This allows faster lookups than the string
  *  reference, but does not permit changing references to be stored.
  *
  *  (Such as those through a transit symbol match, such as #me)
 */
class DirectReference extends Reference
{
	private static final long serialVersionUID = -1786646569320699878L;
	final int index;
	final int timestamp;
	
	public DirectReference( int registry_index, int registry_timestamp )
	{
		index = registry_index;
		timestamp = registry_timestamp;
	}

	public DirectReference( Atom a )
	{
		index = a.index;
		timestamp = a.timestamp;
	}
	
	public Atom get()
	{
		return( Registry.instance.get( index, timestamp ) );
	}
	
	public int getIndex()
	{
		return( index );
	}

	public int getTimestamp()
	{
		return( timestamp );
	}
	
	public Atom getType( Class c )
	{
		try
		{
			Atom a = get();
			
			if( a == null || c.isInstance( a ) )
				return( a );
			else
				return( null );
		}
		catch( OutOfDateReferenceException e )
		{
			return( null );
		}
	}
	
	public int hashCode()
	{
		return( timestamp );
	}
	
	public boolean isValid()
	{
		return( Registry.instance.isReferenceValid( index, timestamp ) );
	}
	
	public Object getReplacement()
	{
		try
		{
			Atom a = Registry.instance.getIfInDatabase( index, timestamp );
			if( a != null )
				return( a.getThis() );
			else
				return( this );
		}
		catch( OutOfDateReferenceException e )
		{
			Log.debug( this, "replacing reference " + toString() + " with empty" );
			return( Reference.EMPTY );
		}
	}
	
	public boolean equals( Object o )
	{
		if( o instanceof DirectReference )
		{
			DirectReference dr = (DirectReference) o;
			return( (index == dr.index) && (timestamp == dr.timestamp) );
		}
		else if( o instanceof Reference )
		{
			Atom a = ((Reference)o).get();
			if( a != null )
				return( (a.index == index) && (a.timestamp == timestamp) );
			else
				return( false );
		}
		else
			return( false );
	}

	public boolean contains( Atom a )
	{
		return( (a.index == index) && (a.timestamp == timestamp) );
	}
	
	public boolean isLoaded()
	{
		return( Registry.instance.indexLoaded( index, timestamp ) );
	}
}

class NamedDirectReference
	extends DirectReference
	implements Symbol
{
	private static final long serialVersionUID = 6458100074905379530L;
	Object key;
	
	public NamedDirectReference( Atom a )
	{
		super( a.index, a.timestamp );
		key = a.getKey();
	}

	private NamedDirectReference( int index, int timestamp, Object key )
	{
		super( index, timestamp );
		this.key = key;
	}
	
	public Object getKey()
	{
		return( key );
	}

	public void setKey( Object k )
	{
		key = k;
	}
	
	public String getName()
	{
		Object o = getKey();
		if( o instanceof String )
			return( (String) o );
		else
			return( o.toString() );
	}
	
	public Object getReplacement()
	{
		Reference r = (Reference) super.getReplacement();
		
		if
		(
			(r != this) &&
		    (r instanceof NamedDirectReference) &&
			(key.equals( ((NamedDirectReference)r).key ))
		)
		{
			return( r );
		}
		
		return( this );
	}
	
	public String toString()
	{
		return( "NamedDirectReference to #" + index + ", " + key.toString() );
	}
}