/* ** j###t ########## #### #### ** j###t ########## #### #### ** j###T "###L J###" ** ######P' ########## ######### ** ######k, ########## T######T ** ####~###L #### ** #### q###L ########## .##### ** #### \###L ########## #####" ** ** $Id: Atom.java,v 1.20 1997/08/02 13:25:04 subtle Exp subtle $ ** ** Class History ** ** Date Name Description ** ---------|------------|----------------------------------------------- ** 22Jun97 subtle made actions static ** 07Aug98 subtle re-write of symbol handling/matching ** */ package key; import key.collections.*; import java.io.*; import java.util.Enumeration; import java.util.StringTokenizer; import java.util.Vector; import java.lang.reflect.InvocationTargetException; /** * The fundamental (large) building block of Key. Most system * accessable objects are based on the Atom. * <P> * The code for the Atom is broken up into several sections: * <UL> * <LI>Structure: dealing with the makeup of the atom, in particular * it's properties. * <LI>Owner: dealing with the player who owns this object * <LI>References: the other Atoms that are using this one * <LI>Symbol: symbolic/reflective matching capabilities * <LI>Parent: the container that holds this Atom * <LI>Management: creation/destruction/load/unload * <LI>Tag: handling of Atom tags * <LI>Interaction: splash event handling, help & information * <LI>Action: static action information * <LI>Statistics: general static statistical information * </UL> * <HR> * <B>Rules for key classes:</B> * <BR> * <I>All classes in the key heirarchy should follow these rules * in order for user-loaded code to remain secure</I> * <P> * All fields shall be either protected or private. Protected * fields are essentially public through the symbol mechanism, * which is capable of enforcing the additional permissions of * the key system when it is used. This means that code within * the key package may access it directly, but user-loaded code * needs to use public methods (below), or use the symbolic system. * <P> * Methods may be public iff they explicitly call the permission * checking system. Protected methods do not have this restriction. */ public class Atom implements Searchable,Symbol,Serializable,key.sql.SQLStorable { private static final long serialVersionUID = -7706021129639899395L; public static final int MAX_KEY_LENGTH = 16; static final AtomicElement[] ELEMENTS = { // String getName(); AtomicElement.construct( Atom.class, String.class, "name", AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY, "the name of an atom is a unique identifier within its container" ), AtomicElement.construct( Atom.class, String.class, "keyType", AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY, "the type of key this atom has" ), // Atom getParent(); AtomicElement.construct( Atom.class, Atom.class, "parent", ".", AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY, "the container (or atomic property) which holds this atom" ), // Atom getOwner(); AtomicElement.construct( Atom.class, Atom.class, "owner", AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY, "the atom which owns this atom. generally a player or null" ), // String getId(); AtomicElement.construct( Atom.class, String.class, "id", AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY, "the fully qualified path of this atom" ), // int getIndex(); AtomicElement.construct( Atom.class, Integer.TYPE, "index", AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY, "the index of this atom" ), // int getTimestamp(); AtomicElement.construct( Atom.class, Integer.TYPE, "timestamp", AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY, "the timestamp of this atom" ), // String getStorageType(); AtomicElement.construct( Atom.class, String.class, "storageType", AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY, "the way this atom is stored" ) }; public static final AtomicStructure STRUCTURE = new AtomicStructure( null, ELEMENTS ); transient AtomicStructure structure; protected PermissionList permissionList; private transient Reference referenceToMe; Reference owner = Reference.EMPTY; private Object tag; private Reference parent; private int parent_type; //protected transient Vector reverseReferences; // these two are stored explicitly transient int index; transient int timestamp; /** referenceCount for implicit references */ private transient int referenceCount; /** * Made available for use by objects and their subclasses. */ protected transient byte state; protected Atom() { index = Registry.instance.allocateTemporaryIndex( this ); // this line is standard for almost all atoms; init(); // deliberately not using setTag() - doing so // would break the one reassignment thats // permitted (for a loadFrom) // // This problem is caused because when an Atom // is constructed, it _must_ be ready to use, // (ie, having a tag), but it also must be loadable // (ie, acknowledging that the tag might already be // in the system). On top of this, we want to prevent // anyone assigning the tag in any circumstances *other* // than these. //tag = new Tag( this ); //initialTag = true; assignInitialOwner(); permissionList = new PermissionList( this ); parent = Reference.EMPTY; } private void readObject( ObjectInputStream ois ) throws IOException { index = ois.readInt(); timestamp = ois.readInt(); Registry.instance.makeTemporarilyAvailable( this ); try { ois.defaultReadObject(); } catch( ClassNotFoundException e ) { throw new UnexpectedResult( e.toString() ); } //initialTag = true; //Registry.instance.registerIndex( this, index ); init(); } private void writeObject( ObjectOutputStream to ) throws IOException { to.writeInt( index ); to.writeInt( timestamp ); to.defaultWriteObject(); } /** * Called after all constructors and everything has been set up - * but not after the atom is loaded, only created. */ protected void constructed() { } /** * Called after all initialisation has been done after the * atom has been loaded. It is important not to try and use * readObject(), since that is called before sub-classes * have been initialised. */ public void loaded() { } void setIndex( int i, int ts ) { index = i; timestamp = ts; } public final int getIndex() { return( index ); } public final int getTimestamp() { return( timestamp ); } public String getStorageType() { return( Registry.instance.getStorageType( index, timestamp ) ); } /** * Subclasses should override this method to return their * own derived structure. */ protected AtomicStructure getDeclaredStructure() { return( STRUCTURE ); } /** * This method returns a reference to this Atom, which * prevents the continual creation of new references. * * This created reference will always be named, that is * a Symbol. * * This will not be the only reference to this atom, there * may be many, so use .equals to compare them, not == */ public final Reference getThis() { return( referenceToMe ); } /** * Private to prevent it being overridden by other init() * methods. */ private void init() { totalAtoms++; structure = getDeclaredStructure(); referenceToMe = Reference.to( this, true ); } void stopBeingTemporary() { Registry.instance.upgradeTemporaryIndex( index, timestamp ); Factory.nonTemporaryFields( this ); } //--- STRUCTURE ---------------------------------------------------------- // // This section of an Atom is reserved for those methods dealing with // the structure of the Atom. Included are general routines to look up // and change the various properties. // /** * Returns an enumeration of all of the properties on this * Atom (the Atom's structure). Used by the 'Dump' command, * for instance. Classes within the atom's heirarchy may * use the protected 'structure' variable instead. */ public final AtomicStructure getStructure() { return( structure ); } //--- OWNER -------------------------------------------------------------- // // This section of an Atom contains those methods dealing with the Atom's // owner. // /** * Sets the owner to a player (if this is being * executed as a player), or as null, otherwise */ void assignInitialOwner() { Player p = Player.getCurrent(); if( p != null ) owner = p.getThis(); else owner = Reference.EMPTY; } /** * A public accessor to a method that can only be overridden * in the local (trusted) package. */ public final void setRecursiveOwner( Atom newOwner ) { setRecursiveOwner_imp( newOwner ); } void setRecursiveOwner_imp( Atom newOwner ) { // anyone can setOwner an // atom before its been added // to a parent if( getParent() != null ) permissionList.check( possessAction ); if( newOwner != null ) { owner = Reference.to( newOwner ); // also set the owner for all final properties // to this owner. Factory.syncOwnerRecursiveFields( this ); } else owner = Reference.EMPTY; } public synchronized final void setOwner( Atom newOwner ) { // anyone can setOwner an // atom before its been added // to a parent if( getParent() != null ) permissionList.check( possessAction ); if( newOwner != null ) { owner = Reference.to( newOwner ); // also set the owner for all final properties // to this owner. Factory.syncOwnerFields( this ); } else owner = Reference.EMPTY; } /** * Returns true if potential owns this atom * (either directly or indirectly) */ public final boolean isOwner( Atom potential ) { Atom ourOwner = owner.get(); if( ourOwner == null ) return false; else if( potential == ourOwner ) return true; else if( ourOwner == this ) return false; else return( ourOwner.isOwner( potential ) ); } public final Atom getOwner() { return( owner.get() ); } public final Reference getOwnerReference() { return( owner ); } //--- SYMBOL ------------------------------------------------------------- // // This section of an Atom contains those methods permitting an Atom to // be accessed symbolically. // public final Object getKey() { return( tag ); } public final void setKey( Object newkey ) { setKey_imp( newkey ); } void setKey_imp( Object newkey ) { if( newkey.toString().indexOf( '^' ) != -1 ) throw new LimitationException( "Colour codes not permitted in object id's" ); if( newkey instanceof String && ((String)newkey).length() > MAX_KEY_LENGTH ) { Log.error( "key length too long: " + ((String)newkey) ); throw new LimitExceededException( "Key length too long. (>" + MAX_KEY_LENGTH + " characters): '" + ((String) newkey) + "'" ); } permissionList.check( modifyAction ); tag = newkey; ((Symbol)referenceToMe).setKey( newkey ); } /** * Returns a printable representation of the name of this * atom. It can contain funky characters not normally * present (for instance, non-resident players are preceeded * with 'VISITOR:') */ public String getName() { if( tag != null ) return( tag.toString() ); else return( "" ); } public final String getKeyType() { if( tag != null ) return( tag.getClass().getName() ); else return( Key.nullString ); } /** * Gets the value of a property by the property name. This is * an efficient function that won't accept more advanced * paths - the exact property name must be given. * <P> * For instance, 'name' is fine, but 'owner.name' will fail, since * this is a path rather than a property name. * * @return the value of the property 'id' * @throws NoSuchPropertyException if that property does not exist */ public final Object getProperty( String id ) throws NoSuchPropertyException { AtomicElement ae = structure.getElement( id ); if( ae != null ) { try { return( ae.getValue( this ) ); } catch( IllegalAccessException e ) { throw new NoSuchPropertyException( e.toString() ); } catch( IllegalArgumentException e ) { throw new UnexpectedResult( e.toString() + " during getProperty( " + id + " );" ); } catch( NullPointerException e ) { throw new UnexpectedResult( e.toString() + " during getProperty( " + id + " );" ); } } else throw new NoSuchPropertyException( "no property '" + id + "' in " + getName() + ":" + Type.typeOf( this ).getName() ); } /** * Sets the value of a property by property name. * * Similar to getProperty, this function will only match exact * property names, as opposed to being able to match pathnames. * * This function also performs checks to ensure that the caller * is permitted to modify this Atom. * * @param id the name of the property * @param value the value to set it to. * @throws NoSuchPropertyException if the property does not exist * @throws TypeMismatchException if the value's type does not match * the type for the property */ public final void setProperty( String id, Object value ) throws NoSuchPropertyException,TypeMismatchException { permissionList.check( modifyAction ); AtomicElement ae = structure.getElement( id ); if( ae != null ) { try { ae.setValue( this, value, null ); } catch( IllegalAccessException e ) { throw new UnexpectedResult( e.toString() ); } } else throw new NoSuchPropertyException( "no property '" + id + "' in " + getName() + ":" + Type.typeOf( this ).getName() ); } public void search( Search s ) throws InvalidSearchException { StringTokenizer st = s.st; if( !st.hasMoreTokens() ) return; // s.result is already == this s.lastAtom = this; String p = st.nextToken(); switch( p.charAt( 0 ) ) { case '.': if( !st.hasMoreElements() ) { s.result = this; return; } else p = st.nextToken(); break; case '/': if( !st.hasMoreElements() ) { s.result = this; return; } else { String n = st.nextToken(); // they can't write 'myname/something' if // we're not a container, although we'll // let them get away with 'myname/.property'. if( n.charAt( 0 ) == '.' ) { // duplicate the code above, // but don't loop it since if someone // writes "///////////." they're obviously // deranged ;) if( st.hasMoreElements() ) p = st.nextToken(); else { s.result = this; return; } } else throw new InvalidSearchException( getId() + " is not a container" ); } } // look up the element here AtomicElement ae = structure.getElement( p ); if( ae == null ) throw new InvalidSearchException( "no such property '" + p + "' in " + getId() ); s.result = ae; } //--- PARENT ------------------------------------------------------------- // // This section of an Atom contains those methods dealing with the Atom's // parent container. // /** * This routine not only sets the parent, but ensures * that this atom is removed from its previous parent, * as well. (after all, there can only be one parent) * * TODO: perhaps make this private, or obsolete altogether, * with the new addParent() */ synchronized final void setParent( Atom newParent, int type ) { // META: no permissions on setParent? /* META if( parent != null ) { if( !(parent instanceof AtomicProperty && ((Atom)((AtomicProperty)parent).getParent()).disposing) ) { if( newParent == null ) permissionList.check( deleteAction ); else permissionList.check( moveAction ); } } */ try { Atom p = parent.get(); if( p != null ) p.noLongerReferencing( this ); } catch( OutOfDateReferenceException e ) { // doesn't matter, we're wiping it anyway parent = Reference.EMPTY; } if( newParent != null ) { Reference r = newParent.getThis(); if( r.equals( parent ) ) { if( newParent != null ) throw new UnexpectedResult( "setParent with our parent called" ); return; } parent = r; parent_type = type; // check the parent's storage type & copy that. switch( Registry.instance.getStorageTypeIndex( parent.getIndex(), parent.getTimestamp() ) ) { case Registry.STORAGE_LOADED: case Registry.STORAGE_UNLOADED: try { // might not be necessary to sync // straight away, but blah. sync(); } catch( IOException e ) { e.printStackTrace( System.out ); } break; case Registry.STORAGE_DB: stopBeingTemporary(); break; case Registry.STORAGE_TEMPORARY: Registry.instance.makeTemporary( this ); break; default: Log.error( "invalid container load status on add" ); break; } } else { resetParentToNull(); } } /** * Used by setParent( null ) and getParent() when parent * points to an invalid reference */ private void resetParentToNull() { parent = Reference.EMPTY; Registry.instance.makeTemporary( this ); } /** * Called on an Atom to erase it */ public final void dispose() { permissionList.check( deleteAction ); // atom being erased Registry.instance.delete( this ); } /** * Called by registry.delete to clean up this atom */ void delete() { // delete parts: will call delete() on each // atomic part. Factory.partDeleteFields( this ); } void noLongerReferencing( Atom a ) { // the name of the atom is the name of the property - // we can look it up based on this AtomicElement ae = structure.getElement( a.getName() ); if( ae != null ) { try { ae.setValue( this, null, this ); } catch( Exception e ) { System.out.println( e.toString() + " during Atom::noLongerReferencing" ); } } else System.out.println( "FAIL: noLongerReferencing " + a.getName() + " from " + getName() + ": no field of that name" ); } public final Atom getParent() { return( parent.get() ); } /** * Determines if a specified atom is the parent of this * atom. This is more efficient than this.getParent() == a, * as it does not load the parent from disk if it isn't * in memory. */ public final boolean isParent( Atom a ) { return( a.getThis().equals( parent ) ); } public final boolean hasParent() { return( parent.isValid() ); } public final void removeParent( Atom old_parent ) { if( isParent( old_parent ) ) setParent( null, AtomicElement.PARENT_TYPE ); else if( old_parent instanceof Container ) // this if() probably not necessary { old_parent.noLongerReferencing( this ); removeSymbolicParent( (Container) old_parent ); } else Log.debug( this, "atom " + old_parent.toString() + " passed into RemoveParent - unexpected" ); } public final void addParent( Atom new_parent, int parent_type ) { if( !parent.isValid() ) { // set this parent to be our // real parent setParent( new_parent, parent_type ); } else if( !isParent( new_parent ) && new_parent instanceof Container ) // final instanceof probably not required addSymbolicParent( (Container) new_parent ); } public final int getParentType() { return( parent_type ); } //--- MANAGEMENT --------------------------------------------------------- // // This section of an Atom contains methods used when loading Atoms in // and out of memory, as well as the creation and destruction of them. // /** * This method should be overridden if * this command takes arguments. */ public void argument( String args ) throws InvalidArgumentException { throw new InvalidArgumentException( "no arguments to type '" + Type.typeFor( getClass() ).getName() + "'" ); } boolean canSwap() { return( true ); } /** * If this is called for a non-distinct atom, it makes this * atom distinct. If called for a distinct atom, it will * do a hard-write to disk. */ public void sync() throws IOException { Registry r = Registry.instance; if( r.getStorageTypeIndex( index, timestamp ) == Registry.STORAGE_LOADED ) r.write( index, timestamp ); else r.syncDistinct( index, timestamp ); } /** * This method is called to notify an Atom that it is being * swapped out of memory. This method should clear any * transient information (such as references from Scapes * to this player, or disconnecting the player). There is * no need for this method to attempt to sync the atom, * this will be done automatically. */ void clearTransient() { } /** * Decrements implicit references */ final void prepareForSwap() { //System.out.println( "decrementing implicit refs on '" + getKey() + "'..." ); Factory.decrementImplicitReferenceCounts( this ); } boolean willbeLockedAfterDecrement() { // META: make this much more advanced, // scan through implicit references and // try to decrement those, as well... if( referenceCount <= 0 ) return false; return true; } /** * Used to lock the atom into memory by the implicit referencing * system. The from parameter exists in case we want * accountability later. */ final void addReference( Atom from ) { referenceCount++; //System.out.println( "added reference in " + getKey() + " from " + from.getKey() + " (now " + referenceCount + ")" ); } /** * Used to lock the atom into memory by the implicit referencing * system. The from parameter exists in case we want * accountability later. */ final void removeReference( Atom from ) { if( referenceCount > 0 ) referenceCount--; //System.out.println( "removed reference in " + getKey() + " from " + from.getKey() + " (now " + referenceCount + ")" ); if( referenceCount <= 0 ) { // trigger a swapout of dependant objects Registry.instance.swapout( this ); } } /** * This is a notification routine that is called when a reference * container adds this atom to it. (A non-reference container * calls addParent). The default implementation does nothing, * but some atoms (such as Rank or Player) may be interested. */ void addSymbolicParent( Container c ) { //System.out.println( getKey() + ".addSymbolicParent( " + c.getKey() + " )" ); } /** * This is a notification routine that is called when a reference * container removes this atom from it. (A non-reference container * calls set_parent). The default implementation does nothing, * but some atoms (such as Rank or Player) may be interested. */ void removeSymbolicParent( Container c ) { //System.out.println( getKey() + ".removeSymbolicParent( " + c.getKey() + " )" ); } public final String getId() { return( getId_kickoff() ); } String getId_kickoff() { return( getId_imp() ); } /** * Another implementation that can only be overridden * in this package. */ String getId_imp() { Atom p = null; try { p = parent.get(); if( p != null ) { switch( parent_type ) { case AtomicElement.PARENT_TYPE: return( p.getId_imp() + "." + getName() ); case Container.PARENT_TYPE: return( p.getId_imp() + "/" + getName() ); } } } catch( OutOfDateReferenceException e ) { // emulates setParent( null ) resetParentToNull(); } return( Key.unlinkedString + getName() ); } public final String getContainedId() { Atom p = parent.get(); if( p != null ) return( p.getSecondLevelId( 0 ) + getName() ); else return( getName() ); } protected String getSecondLevelId( int c ) { Atom p = parent.get(); if( p != null ) return( p.getSecondLevelId( c+1 ) ); else return( "" ); } //--- INTERACTION -------------------------------------------------------- /** * When an effect occurs, people within its * "splash" range are notified (that is, they * can choose to notice or ignore it). This * routine defaults to ignoring any effect, * but you may override it at will to recieve * any effect that it recieves. */ public void splash( Effect e, SuppressionList s ) { // do nothing - ignore this } public String toString() { if( parent.isLoaded() && (getParent() instanceof Container) ) return( "(" + Type.typeOf( this ).getName() + ") " + getId() ); else return( "(" + Type.typeOf( this ).getName() + ") " + getKey() ); } /** * An atoms aspect is what you see when you look at it * META: can be removed 05Nov98, we have object code now. public Paragraph aspect() { return( new TextParagraph( "infinitely small, this can hardly be seen" ) ); } */ /** * Returns *class* help about this atom. That is, help on the properties * that are available to be set on this atom. Ideally, for every type of * atom created this would be overridden (added to) so that help could be * recieved on every property of an atom * */ public Paragraph help() { return( new TextParagraph( "No help available" ) ); } /** * Returns the permission list for this atom. Requires modify * permission - if you don't need it, use "getImmutablePermissionList" * instead. * * @see getImmutablePermissionList */ public final PermissionList getPermissionList() { permissionList.check( modifyAction ); return( permissionList ); } // useful parameter names, hey? public final boolean permissionCheck( Action a, boolean b, boolean c ) { return( permissionList.permissionCheck( a, b, c ) ); } public final PermissionList.Immutable getImmutablePermissionList() { // this is not a security risk because ImmutableQL isn't changeable return( permissionList.getImmutable() ); } public final void checkPermissionList( Action a ) { permissionList.check( a ); } /** * Check if we're permitted to add this atom to the * container (or whatever) a. */ public final void checkAdd( Atom a ) { // it's only a "move" if we do not already // have a valid parent. (Otherwise we're // just adding it to another container as // a reference, which doesn't count. Move // operations need to be remove-then-add, and // if the add fails, it will undo the first // remove. That makes this permission check // style valid. if( !parent.isValid() ) checkPermissionList( moveAction ); } //--- ACTION ------------------------------------------------------------- // // This section of an Atom (present in many Atom's) contains information // specific to each type of Atom detailing the actions that may be // performed upon it. // /** * The list of actions for Atom's. Each sub-class * that wants to have its own possible actions will * need to have their own collection, as well as * override the routines getActions(), containsAction(), * and getAction(). */ protected static StringKeyCollection staticActions; /** * is allowed to change the parent of this atom - ie, * move it around. */ public static Action moveAction; /** * is allowed to modify the properties of this atom. */ public static Action modifyAction; /** * is allowed to setOwnership of this atom. */ public static Action possessAction; /** * is allowed to remove this atom from its parent */ public static Action deleteAction; // initialise the actions list static { staticActions = new StringKeyCollection(); moveAction = newAction( Atom.class, staticActions, "move", true, false ); modifyAction = newAction( Atom.class, staticActions, "modify", true, true ); possessAction = newAction( Atom.class, staticActions, "possess", true, true ); deleteAction = newAction( Atom.class, staticActions, "delete", true, true ); } /** * Returns a list of the actions supported by * this class. This routine will need to be * overridden by sub-classes using actions */ public Enumeration getActions() { return( staticActions.elements() ); } /** * Returns true if the action is supported by * this class. This routine will need to be * overridden by sub-classes using actions */ public boolean containsAction( Action a ) { return( staticActions.contains( a ) ); } /** * Returns the action corresponding to the * supplied name. This routine will need to * be overriden by sub-classes using actions. */ public Action getAction( String name ) { return( (Action) staticActions.get( name ) ); } /** * Adds another action to the available actions in the * collection provided. */ protected static final Action newAction( Class in, Collection actions, String name, boolean expert, boolean serious ) { Action a = new Action( in, name, expert, serious ); try { actions.link( a ); } catch( BadKeyException e ) { e.printStackTrace( System.out ); throw new UnexpectedResult( e.toString() + " while initialising action " + name ); } catch( NonUniqueKeyException e ) { e.printStackTrace( System.out ); throw new UnexpectedResult( e.toString() + " while initialising action " + name ); } return( a ); } //--- STATISTICS --------------------------------------------------------- // // This section contains some static functions that keep some statistical // information about Atom's in general. // private static int totalAtoms; public static int getTotalAtoms() { return( totalAtoms ); } protected void finalize() throws Throwable { super.finalize(); totalAtoms--; //System.out.println( "finalizing atom " + getKey() + ":" + getClass().getName() ); } /** * Just to prevent others from trying to do it. You can't clone * an Atom, as it has a unique identifier, etc. It should be duplicated * through the Factory and field by field. */ public final Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } public Object retrieveField( java.lang.reflect.Field f ) throws IllegalAccessException { return( f.get( this ) ); } public final int getSQLIndex() { return( index ); } public final void setSQLIndex( int i ) { throw new UnexpectedResult( "Trying to set the SQL index of an Atom" ); } }