/* ** j###t ########## #### #### ** j###t ########## #### #### ** j###T "###L J###" ** ######P' ########## ######### ** ######k, ########## T######T ** ####~###L #### ** #### q###L ########## .##### ** #### \###L ########## #####" */ package key; import key.collections.*; import key.util.FilteredEnumeration; import key.util.MultiEnumeration; import java.util.Enumeration; import java.util.StringTokenizer; import java.io.*; import java.util.NoSuchElementException; import java.util.Vector; /** * A container is capable of holding Atoms. */ public class Container extends Atom { private static final long serialVersionUID = 4938263978050622486L; public static final AtomicElement[] ELEMENTS = { // boolean getReferencesOnly(); AtomicElement.construct( Container.class, Boolean.class, "referencesOnly", AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY, "true if this container only holds references" ), // int getCountContents(); AtomicElement.construct( Container.class, Integer.TYPE, "countContents", AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY, "the number of things inside this container" ), AtomicElement.construct( Container.class, String.class, "collectionType", AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY, "the type of the collection" ), // Integer limit; AtomicElement.construct( Container.class, Integer.class, "limit", AtomicElement.PUBLIC_FIELD, "the maximum number of things that may be in this container" ) }; public static final AtomicStructure STRUCTURE = new AtomicStructure( Atom.STRUCTURE, ELEMENTS ); /** * @see key.AtomicElement.PARENT_TYPE */ public static final int PARENT_TYPE = 0; // proper container objects have rooms off them // normal small rooms have exits // I *guess* its possible to have both transient Collection contained; Reference joined = Reference.EMPTY; protected boolean reference; // initially no limit (null) public Integer limit = null; protected Type constrainedType; public Container() { this( false ); } public AtomicStructure getDeclaredStructure() { return( STRUCTURE ); } public Container( boolean ref ) { totalContainers++; reference = ref; contained = new StringKeyCollection(); constrainedType = null; } public Container( boolean ref, Object key, Collection initialCollection ) { totalContainers++; setKey( key ); reference = ref; contained = initialCollection; constrainedType = null; } public Container( Object key, Type constraint ) { this( false ); setKey( key ); if( constraint != null ) setConstraint( constraint ); } private void writeObject( ObjectOutputStream oos ) throws IOException { removeInvalidReferences(); oos.defaultWriteObject(); writeContents( oos ); } public String getCollectionType() { return( contained.getClass().getName() ); } private void readObject( ObjectInputStream ois ) throws IOException { try { ois.defaultReadObject(); readContents( ois ); } catch( ClassNotFoundException e ) { throw new UnexpectedResult( e.toString() ); } totalContainers++; } void setRecursiveOwner_imp( Atom newOwner ) { super.setRecursiveOwner_imp( newOwner ); if( !reference ) { for( Enumeration e = elements(); e.hasMoreElements(); ) { Atom a = (Atom) e.nextElement(); if( a.isParent( this ) ) a.setRecursiveOwner( newOwner ); } } } /** * The argument to a container is the class to constrain * contained objects to */ public void argument( String args ) throws IllegalArgumentException { if( args.length() != 0 ) { try { setConstraint( Type.forName( args ) ); } catch( ClassNotFoundException e ) { throw new IllegalArgumentException( "Could not find class '" + args + "' to constraint the container." ); } } } public Type getConstraint() { return( constrainedType ); } protected void setConstraint( Type c ) { if( (contained.count() == 0) ) { constrainedType = c; } else throw new UnexpectedResult( "trying to constrain a class with " + contained.count() + " elements already in it" ); } public final boolean isReference() { return( reference ); } public boolean canAdd() { if( limit != null && (contained.count() >= limit.intValue()) ) return( false ); return( true ); } /** * This method is called after the permission checking has * been done to actually do the work of adding the atom. * * If this is a reference container, the atom is never * actually loaded. */ protected void addInternal( Reference added ) throws BadKeyException, NonUniqueKeyException { Atom addedAtom = null; // check the limit on this container hasn't been exceeded if( limit != null ) { if( contained.count() >= limit.intValue() ) throw new LimitExceededException( "limit of " + limit.toString() + " exceeded on " + getName() ); } addedAtom = added.get(); if( addedAtom == null ) { System.out.println( "null reference " + added.getClass().getName() + " passed to addInternal." ); } if( constrainedType != null ) { Type addedType = Type.typeOf( addedAtom ); if( !addedType.isA( constrainedType ) ) throw new TypeMismatchException( added.getName() + " is a " + addedType.getName() + ", which is not a " + constrainedType.getName() + ", the required class for " + getName() ); } Container j = (Container) joined.get(); if( j != null ) { j.addInternal( added ); } if( !reference ) addedAtom.addParent( this, PARENT_TYPE ); else addedAtom.addSymbolicParent( this ); contained.link( (Symbol) added ); } /** * Adds the supplied atom and sets its parent to this */ public void add( Atom added ) throws BadKeyException, NonUniqueKeyException { permissionList.check( addToAction ); added.checkAdd( this ); addInternal( added.getThis() ); } public void alias( Atom toAlias ) { permissionList.check( concealInAction ); contained.conceal( (Symbol) toAlias.getThis() ); } public void unalias( Atom toNormal ) { permissionList.check( revealInAction ); contained.reveal( (Symbol) toNormal.getThis() ); } /** Used primarily in the 'Many' command. */ public void rekey( Atom toRekey, AliasListKey newAlk ) { removeInvalidReferences(); // remove the current keys AliasListKey alk; try { alk = (AliasListKey) toRekey.getKey(); } catch( ClassCastException e ) { throw new UnexpectedResult( "You need an aliaslist key atom in order to rekey." ); } if( contained instanceof AliasCollection ) { AliasCollection ac; ac = (AliasCollection) contained; ac.removeSecondaries( alk ); // rekey the atom alk.copySecondaries( newAlk ); ac.addSecondaries( alk, (Symbol) toRekey.getThis() ); } else if( contained instanceof ObjectCollection ) { // rekey the atom alk.copySecondaries( newAlk ); ((ObjectCollection)contained).rekey( toRekey.getThis() ); } else throw new UnexpectedResult( "You need an aliasable container in order to rekey (" + contained.getClass().getName() + ": " + (contained instanceof ObjectCollection) ); } public final void sort() { permissionList.check( sortAction ); removeInvalidReferences(); contained.sort(); } protected void delete() { super.delete(); if( !reference ) { try { for( Enumeration e = elements(); e.hasMoreElements(); ) { Atom a = (Atom)e.nextElement(); a.removeParent( this ); Registry.instance.deleteIfTemporary( a ); } } catch( NoSuchElementException e ) { // this is okay - it happens when we dispose atoms that are // already disposed - during this scan. this exception is // just because we ran out of things to delete in this // circumstance. } } } void stopBeingTemporary() { super.stopBeingTemporary(); for( Enumeration e = elements(); e.hasMoreElements(); ) { Atom a = (Atom) e.nextElement(); a.stopBeingTemporary(); } } /** * removes the atom. if this is its parent container, specifies that the * atom should be disposed of (deleted) */ public void remove( Atom removed ) throws NonUniqueKeyException,NoSuchElementException,BadKeyException { // this is a slightly more complicated permissions // model. we can remove if we've got access to remove // from this group, OR if we've got access to move the // player in question. try { permissionList.check( removeFromAction ); } catch( AccessViolationException e ) { if( !reference ) removed.checkPermissionList( Atom.deleteAction ); else { // we can also remove from a container if // we own the atom being removed - we always // have rights over our own things. Player p = Player.getCurrent(); // this way is easier to read, rather than // negating the expression. if( p != null && removed.isOwner( p ) ) ; // fall through else throw e; } } removeInternal( removed.getThis() ); } protected void removeInternal( Reference removed ) throws NonUniqueKeyException,NoSuchElementException,BadKeyException { if( reference ) { noSideEffectRemove( removed ); // this is a little hacky - it should // really just get() and "removeParent" // like the other one, but that would // require loading all the time, which // wouldn't be much fun. besides, what // are reference containers for, if not // efficiency hacks like this. if( removed.isLoaded() ) removed.get().removeSymbolicParent( this ); } else { Atom a = removed.get(); // will call 'noSideEffectRemove' to remove a.removeParent( this ); } } protected void noSideEffectRemove( Reference removed ) throws NonUniqueKeyException,NoSuchElementException,BadKeyException { contained.unlink( (Symbol) removed ); // this will occasionally throw an exception - generally for the joined // classes - I figure its our responsibility to remove the element // if it gets removed from here. If you're going to step through // reverseReferences and unlink them all, and then unlink the atom // from its parent, unlink it from its parent first, since that will // tidy up and make it one less reverse reference to deal with. Container j = (Container) joined.get(); if( j != null ) { try { j.removeInternal( removed ); } catch( NonUniqueKeyException e ) { Log.debug( this, e.toString() + " while removing from joined" ); } catch( NoSuchElementException e ) { // If you get: // key.Residents - java.util.NoSuchElementException: // joe while removing from joined // // or similar - it just means the person was a // newbie when the server was shut down. Log.debug( this, e.toString() + " while removing from joined" ); } catch( BadKeyException e ) { Log.debug( this, e.toString() + " while removing from joined" ); } } } public void removeByNumber( int c ) throws NonUniqueKeyException,BadKeyException { // this is a slightly more complicated permissions // model. we can remove if we've got access to remove // from this group, OR if we've got access to move the // player in question. try { permissionList.check( removeFromAction ); } catch( AccessViolationException e ) { if( !reference ) { Reference t = (Reference) contained.getElementAt( c ); Atom a = t.get(); a.getPermissionList().check( Atom.deleteAction ); } else throw e; } // TODO: this code below has been added & not // tested to ensure it was really required. if( !reference ) { Reference t = (Reference) contained.getElementAt( c ); Atom a = t.get(); a.removeParent( this ); } contained.removeElementAt( c ); } public final Symbol getElementAt( int n ) { Reference r = (Reference) contained.getElementAt( n ); if( r != null ) return( getReference( r ) ); else return( null ); } public final Symbol getElementAt( String p ) { try { return( getElementAt( Integer.parseInt( p ) ) ); } catch( NumberFormatException e ) { throw new InvalidSearchException( "a number must follow the @ symbol" ); } } // used to be limit( int ) public void setLimit( int amount ) { limit = new Integer( amount ); } public final Object getTrieFor( String m ) { removeInvalidReferences(); return( contained.getTrieFor( m ) ); } public void removeInvalidReferences() { for( Enumeration e = contained.elements(); e.hasMoreElements(); ) { Reference r = (Reference) e.nextElement(); if( !r.isValid() ) { try { noSideEffectRemove( r ); } catch( Exception x ) { System.out.println( x.toString() ); x.printStackTrace( System.out ); } } } } /** * Adds all the symbols in this * container to the provided Container * * This container retains a reference to * its elements, but full ownership (the * elements parents) is given to the new * container. (ie, the parents of all * elements in this container are passed * to the parent container) */ public void addTopLevel( Container addTo ) { joined = addTo.getThis(); for( Enumeration e = referenceElements(); e.hasMoreElements(); ) { try { Reference l; l = (Reference) e.nextElement(); addTo.addInternal( l ); } catch( BadKeyException t ) { Log.error( "invalid key while trying to add to top level", t ); } catch( NonUniqueKeyException t ) { Log.error( "non unique key while trying to add to top level", t ); } } } /** * This function returns Tags? */ public final Enumeration elements() { return( new FilteredEnumeration( contained.elements(), new ReferenceEnumeratorFilter( new ReferenceEnumeratorFilter.EnumeratedThing() { public void noSideEffectRemove( Reference r ) throws NonUniqueKeyException, java.util.NoSuchElementException,BadKeyException { Container.this.noSideEffectRemove( r ); } } , true ) ) ); } public final Enumeration referenceElements() { return( new FilteredEnumeration( contained.elements(), new ReferenceEnumeratorFilter( new ReferenceEnumeratorFilter.EnumeratedThing() { public void noSideEffectRemove( Reference r ) throws NonUniqueKeyException, java.util.NoSuchElementException,BadKeyException { Container.this.noSideEffectRemove( r ); } } , false ) ) ); } public void clearAllElements() { permissionList.check( removeFromAction ); for( Enumeration e = referenceElements(); e.hasMoreElements(); ) { Reference t = (Reference) e.nextElement(); try { removeInternal( t ); } catch( NonUniqueKeyException except ) { throw new UnexpectedResult( except.toString() ); } catch( BadKeyException except ) { throw new UnexpectedResult( except.toString() ); } } contained.deallocate(); } 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(); //System.out.println( "container::search recvd '" + p + "'" ); Reference o = null; switch( p.charAt( 0 ) ) { case '.': if( !st.hasMoreElements() ) { s.result = this; return; } p = st.nextToken(); //System.out.println( "(.) recvd '" + p + "'" ); { AtomicElement ae = structure.getElement( p ); if( ae == null ) throw new InvalidSearchException( "no such property '" + p + "' in " + getId() ); s.result = ae; } return; case '@': // if it has a '@' in it, get elementAt if( !st.hasMoreElements() ) throw new InvalidSearchException( "search string cannot end with a @" ); p = st.nextToken(); try { o = (Reference) contained.getElementAt( Integer.parseInt( p ) ); } catch( NumberFormatException e ) { throw new InvalidSearchException( "a number must follow the @ symbol" ); } break; case '/': // if it ends with a '/', ignore the trailing slash if( !st.hasMoreElements() ) return; p = st.nextToken(); if( p.charAt(0) == '.' ) { if( !st.hasMoreTokens() ) return; // ignore it, as above // sometimes people write 'myname/.property', // and this is permitted, but we need a hack // for it. p = st.nextToken(); { AtomicElement ae = structure.getElement( p ); if( ae == null ) throw new InvalidSearchException( "no such property '" + p + "' in " + getId() ); s.result = ae; } return; } // deliberate fall through default: if( p.charAt( 0 ) == Key.nullEntry ) { // special case for matching an element without a name p = ""; } // look up the atom here o = (Reference) contained.getExact( p ); break; } if( o == null ) throw new InvalidSearchException( "no such element '" + p + "' in " + getId() ); if( !o.isValid() ) { try { noSideEffectRemove( o ); } catch( Exception x ) { throw new UnexpectedResult( x ); } throw new InvalidSearchException( "no such element (invalid) '" + p + "' in " + getId() ); } s.result = o; } protected void readContents( ObjectInputStream from ) throws IOException { try { contained = (Collection) from.readObject(); } catch( ClassNotFoundException e ) { throw new UnexpectedResult( "Incompatible change: " + e.toString() + " during container::readContents" ); } } protected void writeContents( ObjectOutputStream to ) throws IOException { to.writeObject( contained ); } protected String getSecondLevelId( int c ) { Atom p = getParent(); if( p == null || getParentType() == PARENT_TYPE ) return( getName() + ((c==0)?'/':':') ); else return( p.getSecondLevelId( c + 1 ) ); } public final int count() { return( contained.count() ); } public final boolean getReferencesOnly() { return( reference ); } public final int getCountContents() { return( count() ); } // if this was false, the container would // use a . match if there aren't any leading // tokens on a getSymbol protected boolean assumedContents = true; public final Object getReferenceElement( String s ) { Reference r = (Reference) contained.get( s ); if( r == null || r.isValid() ) return( r ); else { try { noSideEffectRemove( r ); } catch( Exception x ) { throw new UnexpectedResult( x ); } return( null ); } } public final Object getElement( String s ) { try { if( s.charAt( 0 ) == '@' ) return( getElementAt( s.substring( 1 ) ) ); } catch( StringIndexOutOfBoundsException e ) { } Object o = contained.get( s ); if( o instanceof Reference ) { Reference r = (Reference) o; if( r != null ) return( getReference( r ) ); else return( null ); } else return( o ); } private final Atom getReference( Reference r ) { try { return( r.get() ); } catch( OutOfDateReferenceException e ) { System.out.println( "CONTAINER: out of date reference to " + r.getName() + " removed" ); try { noSideEffectRemove( r ); } catch( Exception x ) { throw new UnexpectedResult( x ); } return( null ); } } public final Object getExactElement( String s ) { try { if( s.charAt( 0 ) == '@' ) return( getElementAt( s.substring( 1 ) ) ); } catch( StringIndexOutOfBoundsException e ) { } Reference o = (Reference) contained.getExact( s ); if( o != null ) return( getReference( o ) ); else return( null ); } public void noLongerReferencing( Atom a ) { if( a.getParentType() == PARENT_TYPE ) { try { noSideEffectRemove( a.getThis() ); } catch( NonUniqueKeyException e ) { Log.error( "noLongerReferencing called for an atom that we're not referencing", e ); } catch( BadKeyException e ) { Log.error( "noLongerReferencing called for an atom that we're not referencing", e ); } } else super.noLongerReferencing( a ); } public final boolean contains( Atom a ) { return( contained.contains( (Symbol) a.getThis() ) ); } public final boolean containsReference( Reference r ) { return( contained.contains( r ) ); } //--- actions ---// protected static StringKeyCollection staticActions; /** is allowed to add objects to this container */ public static Action addToAction; /** is allowed to remove objects from this container */ public static Action removeFromAction; /** can conceal atoms in this container */ public static Action concealInAction; /** can reveal atoms in this container */ public static Action revealInAction; /** can sort atoms in this container */ public static Action sortAction; static { staticActions = new StringKeyCollection(); addToAction = newAction( Container.class, staticActions, "addTo", true, true ); removeFromAction = newAction( Container.class, staticActions, "removeFrom", true, true ); concealInAction = newAction( Container.class, staticActions, "concealIn", true, true ); revealInAction = newAction( Container.class, staticActions, "revealIn", true, true ); sortAction = newAction( Container.class, staticActions, "sort", true, true ); } public Enumeration getActions() { return( new MultiEnumeration( staticActions.elements(), super.getActions() ) ); } public boolean containsAction( Action a ) { return( staticActions.contains( a ) || super.containsAction( 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 ) { Action a = (Action) staticActions.get( name ); if( a == null ) return( super.getAction( name ) ); else return( a ); } private static int totalContainers; public static int getTotalContainers() { return( totalContainers ); } protected void finalize() throws Throwable { super.finalize(); totalContainers--; } }