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
**  ---------|------------|-----------------------------------------------
**  28Jan98    subtle       created
**  08Aug98    subtle       TODO: Make flag selection & verification
**                          tighter.  ie, 'REFERENCE' should only be
**                          settable iff f instanceof Reference.  Also,
**                          it can probably be automatically determined,
**                          without it needing to be stated.
**
*/

package key;

import java.lang.reflect.*;

/**
  *  This class represents the basic building block of an Atom in
  *  Key.  (An Atom, in turn, is the building block of the larger 
  *  structures).
  * <P>
  *  Each class of Atom has a static reference to an AtomicStructure,
  *  which holds all of the AtomicElements describing that Atom class.
  * <P>
  *  AtomicElements use Java Reflection to directly access the values
  *  held within the Atom, and as such, Atoms have the responsibilities
  *  to provide the correct fields and methods for their structure, (as
  *  held in their AtomicStructure).
  * <P>
  *  Each AtomicElement has a set of flags which determine the requisite
  *  support from Atom.  Specifically, for an AtomicElement with name
  *  <I>X</I> and type <I>classY</I>:
  * <P>
  * <TABLE>
  *
  * <TH>
  *   <TD>type</TD><TD>readonly?</TD><TD>reference?</TD><TD>Requirements</TD>
  * </TH>
  *
  * <TR>
  *   <TD>public field</TD><TD>n/a</TD><TD>no</TD>
  *     <TD><UL>
  *       <LI>public classY X;</LI>
  *     </UL></TD>
  * </TR>
  *
  * <TR>
  *   <TD>public field</TD><TD>n/a</TD><TD>yes</TD>
  *     <TD><UL>
  *       <LI>public Reference X;</LI>
  *     </UL></TD>
  * </TR>
  *
  * <TR>
  *   <TD>public accessors</TD><TD>yes</TD><TD>n/a</TD>
  *     <TD><UL>
  *       <LI>public classY getX();</LI>
  *     </UL></TD>
  * </TR>
  *
  * <TR>
  *   <TD>public accessors</TD><TD>no</TD><TD>no</TD>
  *     <TD><UL>
  *       <LI>public classY getX();</LI>
  *       <LI>public void setX( classY );</LI>
  *     </UL></TD>
  * </TR>
  *
  * <TR>
  *   <TD>public accessors</TD><TD>no</TD><TD>yes</TD>
  *     <TD><UL>
  *       <LI>public classY getX();</LI>
  *       <LI>public void setX( classY );</LI>
  *       <LI>public void setX( String, Atom relTo );</LI>
  *     </UL></TD>
  * </TR>
  * </TABLE>
  * <P>
  *  An AtomicElement may only be flagged "Reference" iff the
  *  type of the element is a subclass of Atom.  In other words,
  *  a Reference may only point to another Atom.
  * <P>
  *  The setX( String, Atom ) accessor (for references) accepts a
  *  string as the Id of an atom in the hierarchy.  This string is
  *  'relative' to the relTo parameter.  If you are using a
  *  "Reference" underlying the accessors, this is easy to implement
  *  by simply passing the call through to the set method in the
  *  underlying Reference object.
 */
public abstract class AtomicElement implements Searchable
{
	/**
	  * @see key.Container.PARENT_TYPE
	 */
	public static final int PARENT_TYPE = 1;
	
	/**
	  *  If (flags & PUBLIC_FIELD) == PUBLIC_FIELD, then
	  *  this element is accessable through a reflection
	  *  field.
	 */
	public static final int PUBLIC_FIELD = (1<<0);

	/**
	  *  If (flags & PUBLIC_ACCESSORS) == PUBLIC_ACCESSORS,
	  *  then this element is accessable through some
	  *  accessor (get/set) methods.
	 */
	public static final int PUBLIC_ACCESSORS = (1<<1);
	
	/**
	  *  If this flag is selected then you cannot call
	  *  'set' on this element.
	 */
	public static final int READ_ONLY = (1<<2);
	
	/**
	  *  If this flag is selected then this element is a
	  *  reference to an Atom somewhere else in the Key
	  *  hierarchy.  The class of the referenced element
	  *  _must_ be an Atom for this to be the case.
	 */
	public static final int REFERENCE = (1<<3);
	
	/**
	  *  This flag does not need to be set explicitly.  If
	  *  this flag is set, it means that the class of the
	  *  value is atomic.  This means different things, dependant
	  *  upon whether this field is also a reference.
	  * <P>
	  *  If this element is not a reference, this means that atoms
	  *  that are assigned in here will have their parent, owner,
	  *  and name set correctly.  Atoms that are un-assigned from
	  *  here will be destroyed.
	  * <P>
	  *  An element cannot be a reference unless it is also an
	  *  atom.  (ie, REFERENCE implies ATOMIC)
	 */
	public static final int ATOMIC = (1<<4);
	
	/**
	  *  This flag indicates that this field is a calculated
	  *  value, derived from other properties.  Having this set
	  *  will prevent some commands (Dump) from listing it
	  *  explicitly.
	  * <P>
	  *  For instance, the Player class has both a 'name' field
	  *  and a 'title' field.  However, for user-interface reasons,
	  *  a 'titledName' read-only method is also available that
	  *  returns the concatenation of these two fields.  There is no
	  *  need, however, to output this field in a 'dump' of the entire
	  *  class: it is redundant information.
	 */
	public static final int GENERATED = (1<<5);
	
	private int hashCode;
	private String lowerName;
	private String fieldName;
	private int flags;
	private String description;
	
	protected Class classOf = null;
	protected Type typeOf = null;
	protected AtomicSpecial special = null;
	
	protected AtomicElement( String fName, String matchName, int ourFlags, String desc )
	{
		fieldName = fName;
		lowerName = matchName.toLowerCase();
		flags = ourFlags;
		description = desc;
		
			//  calculate a hashcode for slightly faster
			//  compares / finds.
		hashCode = lowerName.hashCode();
	}
	
	public final String getLowerName()
	{
		return( lowerName );
	}
	
	public final String getName()
	{
		return( fieldName );
	}
	
	public final Object getKey()
	{
		return( fieldName );
	}
	
	public final Type getTypeOf()
	{
		return( typeOf );
	}

	public final Class getClassOf()
	{
		return( classOf );
	}
	
	public final void setKey( Object o )
	{
		throw new UnexpectedResult( "cannot set the key of an element" );
	}
	
	public final void search( Search s ) throws InvalidSearchException
	{
			//  evaluate this result
		try
		{
			s.result = getValue( s.lastAtom );
		}
		catch( Exception e )
		{
				//  this could be enhanced to make error messages better,
				//  but lets not bother at the moment
			throw new InvalidSearchException( e.toString() );
		}
	}
	
	public final int getHashCode()
	{
		return( hashCode );
	}
	
	public final boolean isReadOnly()
	{
		return( (flags & READ_ONLY) == READ_ONLY );
	}
	
	public final boolean isReference()
	{
		return( (flags & REFERENCE) == REFERENCE );
	}

	public final boolean isAtomic()
	{
		return( (flags & ATOMIC) == ATOMIC );
	}
	
	public final boolean isGenerated()
	{
		return( (flags & GENERATED) == GENERATED );
	}
	
	public abstract Object getValue( Atom on ) throws IllegalAccessException, IllegalArgumentException, NullPointerException;
	public abstract void setValue( Atom on, Object newValue, Atom relativeTo ) throws IllegalAccessException, IllegalArgumentException, NullPointerException;

	public Object getBasicValue( Atom on ) throws IllegalAccessException, IllegalArgumentException, NullPointerException
	{
		return( getValue( on ) );
	}
	
	public AtomicSpecial getSpecial()
	{
		return( special );
	}
	
	/**
	  *  This routine makes sure that, when an Atom atomic element is
	  *  being used on an Atom, the parent, name, and owner are
	  *  set correctly to avoid permissions problems.
	 */
	public void atomicFrontend( Atom on, Object newValue )
	{
		if( isAtomic() && !isReference() )
		{
			try
			{
				Atom old = (Atom) getValue( on );
				
				if( old != null )
				{
					old.dispose();
					old = null;
				}
			}
			catch( IllegalAccessException e )
			{
				Log.error( e );
			}
			
			if( newValue != null )
			{
					//  we need to set up the parent, name & owner
				Atom a = (Atom) newValue;
				
				a.setKey( fieldName );
				a.setOwner( on.getOwner() );
				
					// META:  Review parent structure here
				a.setParent( on, PARENT_TYPE );
			}
		}
	}
	
	/**
	  *  This routine is called before the 'set' routine
	  *  in any subclass to check the preconditions for a
	  *  call to 'set' (such as this field not being read-only)
	 */
	protected final Object checkSet( Atom on, Object newValue )
	{
		if( isReadOnly() )
			throw new AccessViolationException( this, "attempting to set read only property: " + getName() + " with " + newValue );
		
		on.permissionList.check( Player.modifyAction );
		
		if( special != null )
			return( special.validateNewValue( newValue ) );
		else
			return( newValue );
	}
	
	/**
	  *  Standard abstract factory to create the correct implementation
	  *  subclass based on the provided flags.
	 */
	public static AtomicElement construct( Class in, Class type, String name, int flags, String desc )
	{
		return( construct( in, type, name, name, flags, desc, null ) );
	}
	
	public static AtomicElement construct( Class in, Class type, String name, String matchName, int flags, String desc )
	{
		return( construct( in, type, name, matchName, flags, desc, null ) );
	}
	
	public static AtomicElement construct( Class in, Class type, String name, String matchName, int flags, String desc, AtomicSpecial special )
	{
		AtomicElement ae = null;
		
		try
		{
			if( (flags & PUBLIC_FIELD) == PUBLIC_FIELD )
			{
				Field f = in.getDeclaredField( name );
				
				if( f == null )
				{
					Log.error( "AtomicElement: unknown field type, '" + name + "' in " + in.getName() );
					return null;
				}
				else
				{
					if( Reference.class.isAssignableFrom( f.getType() ) )
					{
						flags |= REFERENCE;
						
							//  just a quick sanity check
						if( !Atom.class.isAssignableFrom( type ) )
							System.out.println( "  !!" + type.getName() + " is not a subclass of Atom - FAILURE condition" );
						
						ae = new ReferenceFieldAtomicElement( in, type, name, matchName, flags, desc );
					}
					else
					{
						if( (flags & REFERENCE) == REFERENCE )
						{
							System.out.println( "incorrectly set REFERENCE flag in field " + f.getName() + " on " + in.getName() );
							flags &= ~REFERENCE;
						}
						ae = new FieldAtomicElement( in, type, name, matchName, flags, desc );
					}
				}
				
				if( Atom.class.isAssignableFrom( type ) )
					ae.flags |= ATOMIC;
			}
			else if( (flags & PUBLIC_ACCESSORS) == PUBLIC_ACCESSORS )
			{
				ae = new AccessorAtomicElement( in, type, name, matchName, flags, desc );
			}
			else
			{
				Log.error( "AtomicElement: problem with field '" + name + "' in " + in.getName() );
				return null;
			}
		}
		catch( Exception e )
		{
			e.printStackTrace();
			Log.error( "AtomicElement: while building atomic structure for " + in.getName() + " (field " + name + ")", e );
		}
		
		if( !(Atom.class.isAssignableFrom( type )) && ae.isReference() )
		{
			throw new UnexpectedResult( "reference to a non-atomic property attempted in class " + in.getName() );
		}
		
		if( special != null )
		{
			if( special.canUseWith( ae ) )
				ae.special = special;
			else
				throw new UnexpectedResult( "invalid special in " + ae.getName() + ", class " + in.getName() );
		}
		
		return( ae );
	}
	
	public String toString()
	{
		StringBuffer sb = new StringBuffer();
		
		if( isReadOnly() )
			sb.append( "[readonly] " );
		
		sb.append( asString() );
		
		return( sb.toString() );
	}

	public abstract String asString();

	public final Object clone() throws CloneNotSupportedException
	{
		throw new CloneNotSupportedException();
	}
}

class FieldAtomicElement extends AtomicElement
{
	Field theField;
	
	public FieldAtomicElement( Class in, Class type, String name, String matchName, int flags, String desc ) throws NoSuchFieldException, SecurityException
	{
		super( name, matchName, flags, desc );
		
		theField = in.getDeclaredField( name );
		classOf = theField.getType();
		typeOf = Type.typeFor( classOf );
	}
	
	public Object getValue( Atom on ) throws IllegalAccessException, IllegalArgumentException, NullPointerException
	{
		return( theField.get( on ) );
	}

	public void setValue( Atom on, Object newValue, Atom relTo ) throws IllegalAccessException, IllegalArgumentException, NullPointerException
	{
		newValue = checkSet( on, newValue );
		atomicFrontend( on, newValue );
		
			//  the VM will check the type for us, here - we don't
			//  have to do it.
		theField.set( on, newValue );
	}
	
	public String asString()
	{
		return( theField.toString() );
	}
}

final class ReferenceFieldAtomicElement extends FieldAtomicElement
{
	public ReferenceFieldAtomicElement( Class in, Class type, String name, String matchName, int flags, String desc ) throws NoSuchFieldException, SecurityException
	{
		super( in, type, name, matchName, flags, desc );
		
		classOf = type;
		typeOf = Type.typeFor( type );
	}
	
	public Object getValue( Atom on ) throws IllegalAccessException, IllegalArgumentException, NullPointerException
	{
		Reference r = (Reference) super.getValue( on );
		
		if( r != null )
		{
			try
			{
				return( r.get() );
			}
			catch( OutOfDateReferenceException e )
			{
				theField.set( on, Reference.EMPTY );
				return( null );
			}
		}
		else
			return( null );
	}

	public Object getBasicValue( Atom on ) throws IllegalAccessException, IllegalArgumentException, NullPointerException
	{
		return( super.getValue( on ) );
	}
	
	public void setValue( Atom on, Object newValue, Atom relTo ) throws IllegalAccessException, IllegalArgumentException, NullPointerException
	{
		newValue = checkSet( on, newValue );
		
		if( newValue == null )
		{
			theField.set( on, Reference.EMPTY );
		}
		else if( classOf.isInstance( newValue ) )
		{
			theField.set( on, ((Atom)newValue).getThis() );
		}
		else if( newValue instanceof String )
		{
				//  the new way does the resolving in the later call
			theField.set( on, Reference.to( (String) newValue, relTo ) );
		}
		else
			throw new IllegalArgumentException( "attempting to set reference with incompatible type." );
	}
}

/**
  *  Permits key to use accessor methods to get and set a property.
  *
  *  Accessor methods must be declared:
  *
  *    public <whatever> get<Property>()
  *    public void set<Property>( Object newValue )
 */
final class AccessorAtomicElement extends AtomicElement
{
	private static final Object[] emptyObjectArray = new Object[0];
	private static final Class[] emptyClassArray = new Class[0];
	Method getMethod;
	Method setMethod;
	Method setIdMethod = null;
	
	public AccessorAtomicElement( Class in, Class type, String name, String matchName, int flags, String desc ) throws NoSuchMethodException, SecurityException
	{
		super( name, matchName, flags, desc );
		
		String cappedName = name.substring( 0, 1 ).toUpperCase() + name.substring( 1 );
		
		getMethod = in.getDeclaredMethod( "get" + cappedName, emptyClassArray );
		
		if( !isReadOnly() )
		{
			Class[] setclasses = new Class[1];
			setclasses[0] = type;
			setMethod = in.getDeclaredMethod( "set" + cappedName, setclasses );
			
			if( isReference() )
			{
				setclasses = new Class[2];
				setclasses[0] = String.class;
				setclasses[1] = Atom.class;
				
				setIdMethod = in.getDeclaredMethod( "set" + cappedName, setclasses );
			}
		}
		else
			setMethod = null;
		
		classOf = getMethod.getReturnType();
		typeOf = Type.typeFor( classOf );
	}
	
	public Object getValue( Atom on ) throws IllegalAccessException, IllegalArgumentException, NullPointerException
	{
		try
		{
			return( getMethod.invoke( on, emptyObjectArray ) );
		}
		catch( InvocationTargetException e )
		{
			Throwable x = e.getTargetException();
			
			if( x instanceof RuntimeException )
				throw (RuntimeException) x;
			else
				throw new UnexpectedResult( x.toString() );
		}
	}
	
	public void setValue( Atom on, Object newValue, Atom relTo ) throws IllegalAccessException, IllegalArgumentException, NullPointerException
	{
		try
		{
			newValue = checkSet( on, newValue );
			atomicFrontend( on, newValue );
			
				//  setIdMethod is != null iff isReference()
			if( setIdMethod != null && newValue instanceof String )
			{
				Object[] setobjects = new Object[2];
				setobjects[0] = newValue;
				setobjects[1] = relTo;
				setIdMethod.invoke( on, setobjects );
			}
			else
			{
				Object[] setobjects = new Object[1];
				setobjects[0] = newValue;
				setMethod.invoke( on, setobjects );
			}
		}
		catch( InvocationTargetException e )
		{
			Throwable x = e.getTargetException();
			
			if( x instanceof RuntimeException )
				throw (RuntimeException) x;
			else
				throw new UnexpectedResult( x.toString() );
		}
	}
	
	public String asString()
	{
		return( getMethod.toString() );
	}
}