/*
** 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() );
}
}