/*
** 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.Trie;
import java.util.StringTokenizer;
import java.util.Hashtable;
import java.util.Enumeration;
import java.io.*;
/**
* The base class for a command also includes some nice utility
* routines that command writers tend to need
*/
public abstract class Command extends Atom implements Commandable
{
private static final long serialVersionUID = 7107251377754958246L;
public static final AtomicElement[] ELEMENTS =
{
AtomicElement.construct( Command.class, String.class, "usage",
AtomicElement.PUBLIC_FIELD,
"the usage specifier for this command" ),
AtomicElement.construct( Command.class, Boolean.TYPE, "disabled",
AtomicElement.PUBLIC_FIELD,
"true if this command can't be used (usually set when something goes wrong)" )
};
public static final AtomicStructure STRUCTURE = new AtomicStructure( Atom.STRUCTURE, ELEMENTS );
protected String usage = "~unspecified~";
protected boolean disabled = false;
public Command()
{
}
public AtomicStructure getDeclaredStructure()
{
return( STRUCTURE );
}
public boolean isDisabled()
{
return( disabled );
}
public void disable()
{
disabled = true;
}
public void usage( InteractiveConnection ic )
{
ic.send( "Format: " + getName() + " " + usage );
}
public void usage( InteractiveConnection ic, CategoryCommand caller )
{
ic.send( "Format: " + getQualifiedName( caller ) + " " + usage );
}
public final String getQualifiedName( CategoryCommand caller )
{
if( caller instanceof Atom )
return( ((Atom)caller).getName() + " " + getName() );
else
return( getName() );
}
public String getWhichId()
{
return( getId() );
}
/**
* If this method is not overridden, it calls
* the others. Only override *one*, depending on
* what arguments you want to recieve
* <p>
* Presumably you'll want to do one or the other,
* overload the method you'd prefer
* @param p the player who is executing the command
* @param args a tokenizer (whitespace) for the command arguments
* @param fullLine the complete line that was typed
* @param caller the parent CategoryCommand that contains this command
* @param ic the interactiveConnection to send the results to
*/
public abstract void run( Player p, StringTokenizer args, String fullLine, CategoryCommand caller, InteractiveConnection ic, Flags flags ) throws IOException;
/**
* An atom is returned in case they are like 'player,player,player',
* so it will return a scape.
*/
public static final Splashable getPlayerInside( Player p, InteractiveConnection ic, String name, Scape inside )
{
Object t = inside.getPlayer( p, name );
if( t == null )
{
ic.sendError( "Cannot find player '" + name + "' online" );
return null;
}
else if( t instanceof Splashable )
{
return( (Splashable) t );
}
else if( t instanceof Trie )
{
// its more efficient to only work with online
// players - ie, prefer only holds when they're
// around - because its a smaller list to scan
for( Enumeration e = ((Trie)t).elements(); e.hasMoreElements(); )
{
Player o = (Player) e.nextElement();
if( p.getPrefer().containsPlayer( o ) )
return( o );
}
int i = ((Trie)t).length();
if( i > 10 )
ic.sendError( i + " multiple matches (not listed)" );
else
ic.sendError( "Multiple matches: " + ((Trie)t).contents() );
return null;
}
else
{
ic.sendError( "'" + name + "' is not a player" );
return null;
}
}
/**
* The basic concept is this: The routine returns a player, or null.
* If it returns null, it has already output an error message to the
* player involved, so you don't have to. (just return)
*
* An atom is returned in case they are like 'player,player,player',
* so it will return a scape
*/
public static final Splashable getOnlinePlayer( Player p, InteractiveConnection ic, String name )
{
if( name.equalsIgnoreCase( "me" ) )
return( p );
else
return( getPlayerInside( p, ic, name, Key.instance() ) );
}
public static final Symbol getReferenceSymbolInside( InteractiveConnection ic, String s, Atom inside )
{
try
{
Object o = new Search( s, inside, false, true ).result;
if( o == null )
ic.sendError( "Could not find '" + s + "'" );
else
{
if( o instanceof Atom || o instanceof Reference )
return( (Symbol) o );
else if( o instanceof Trie )
{
int i = ((Trie)o).length();
if( i > 10 )
ic.sendError( i + " multiple matches (not listed)" );
else
ic.sendError( "Multiple matches: " + ((Trie)o).contents() );
}
else
ic.sendError( "'" + s + "' is non-atomic" );
}
}
catch( NoSuchPropertyException e )
{
ic.sendError( e.getMessage() );
}
return( null );
}
public static final Atom getSymbolInside( InteractiveConnection ic, String s, Atom inside )
{
try
{
Object o = new Search( s, inside ).result;
if( o == null )
ic.sendError( "Could not find '" + s + "'" );
else
{
if( o instanceof Atom )
return( (Atom) o );
else if( o instanceof Trie )
{
int i = ((Trie)o).length();
if( i > 10 )
ic.sendError( i + " multiple matches (not listed)" );
else
ic.sendError( "Multiple matches: " + ((Trie)o).contents() );
}
else
ic.sendError( "'" + s + "' is non-atomic" );
}
}
catch( NoSuchPropertyException e )
{
ic.sendError( e.getMessage() );
}
return( null );
}
/**
* A slightly higher level routine - will match the
* symbol and give error messages as appropriate. It
* is not necessary to check for anything other than
* the return being null, in which case an error
* has already been sent to the provided connection.
*/
public static final Atom getSymbol( InteractiveConnection ic, String s )
{
return( getSymbolInside( ic, s, Key.instance() ) );
}
public static final Reference getReferenceElementInside( InteractiveConnection ic, String s, Container inside )
{
Object o = inside.getReferenceElement( s );
if( o != null )
{
if( o instanceof Atom )
return( ((Atom)o).getThis() );
else if( o instanceof Reference )
return( (Reference) o );
else if( o instanceof Trie )
{
int i = ((Trie)o).length();
if( i > 10 )
ic.sendError( i + " multiple matches (not listed)" );
else
ic.sendError( "Multiple matches: " + ((Trie)o).contents() );
}
else
ic.sendError( "'" + s + "' is non-atomic or doesn't match required type" );
}
return( null );
}
public static final Atom getElementInside( InteractiveConnection ic, String s, Type type, Container inside )
{
Object o = inside.getElement( s );
if( o != null )
{
if( o instanceof TransitAtom )
o = ((TransitAtom)o).getRealAtom();
if( o instanceof Atom && Type.typeOf( o ).isA( type ) )
return( (Atom) o );
else if( o instanceof Trie )
{
int i = ((Trie)o).length();
if( i > 10 )
ic.sendError( i + " multiple matches (not listed)" );
else
ic.sendError( "Multiple matches: " + ((Trie)o).contents() );
}
else
ic.sendError( "'" + s + "' is non-atomic or doesn't match required type" );
}
else
ic.sendError( "Couldn't find '" + s + "'" );
return( null );
}
public static final Thing getObjectFromInventory( Player p, InteractiveConnection ic, String name )
{
return( getObjectIn( p, p.getInventory(), ic, name, "You don't seem to be holding that object." ) );
}
public static final Thing getObjectFromLocation( Player p, InteractiveConnection ic, String name )
{
return( getObjectIn( p, p.getLocation(), ic, name, "That object isn't in this room." ) );
}
public static final Thing getObjectIn( Player p, Container c, InteractiveConnection ic, String name, String notFound )
{
Object temp = c.getElement( name );
if( temp == null )
{
if( notFound != null )
ic.sendFeedback( notFound );
}
else
{
if( temp instanceof TransitAtom )
temp = ((TransitAtom)temp).getRealAtom();
if( !(temp instanceof Thing) )
{
if( temp instanceof Trie )
ic.sendError( "Multiple matches: " + ((Trie)temp).contents() );
else if( notFound != null )
ic.sendError( "This object does not seem to be one of the primitive types of matter." );
}
else
return( (Thing) temp );
}
return( null );
}
public static final Atom getElementInside( InteractiveConnection ic, String s, Container inside )
{
Object o = inside.getElement( s );
if( o != null )
{
if( o instanceof TransitAtom )
o = ((TransitAtom)o).getRealAtom();
if( o instanceof Atom )
return( (Atom) o );
else if( o instanceof Trie )
{
int i = ((Trie)o).length();
if( i > 10 )
ic.sendError( i + " multiple matches (not listed)" );
else
ic.sendError( "Multiple matches: " + ((Trie)o).contents() );
}
else
ic.sendError( "'" + s + "' is non-atomic" );
}
else
ic.sendError( "Cannot find '" + s + "'" );
return( null );
}
public static final Atom getSymbolInside( InteractiveConnection ic, String s, Type type, Atom i )
{
Atom a = getSymbolInside( ic, s, i );
if( a != null )
{
if( Type.typeOf( a ).isA( type ) )
return( a );
else
ic.sendError( a.getName() + " is not a " + type.getName() );
}
return( null );
}
public static final Atom getSymbol( InteractiveConnection ic, String s, Type type )
{
return( getSymbolInside( ic, s, type, Key.instance() ) );
}
/**
* This routine will return a Player or null.
*/
public static final Player getPlayer( InteractiveConnection ic, String name )
{
Object t = Key.instance().getResidents().getElement( name );
if( t == null )
{
ic.sendError( "No player of the name '" + name + "'" );
return null;
}
else if( t instanceof Player )
{
return( (Player) t );
}
else if( t instanceof Trie )
{
int i = ((Trie)t).length();
if( i > 10 )
ic.sendError( i + " multiple matches (not listed)" );
else
ic.sendError( "Multiple matches: " + ((Trie)t).contents() );
return null;
}
ic.sendError( "'" + name + "' is not a player" );
return null;
}
public final String nextArgument( StringTokenizer st, InteractiveConnection ic )
throws NotEnoughParametersException
{
if( st.hasMoreTokens() )
return( st.nextToken() );
usage( ic );
throw new NotEnoughParametersException();
}
public String getUsage()
{
return( usage );
}
/**
* This routine is notified just before a command is executed.
* If it returns this (the default), then run() will be called.
* If it returns null, another matching command will be looked
* for (this might provide a neat way of disabling commands).
* If it returns another command, that command has getMatch
* called on it, and the process repeats. This allows for
* CategoryCommands to be implemented.
*/
public Commandable getMatch( final Player p, key.util.StringTokenizer st )
{
return( this );
}
public Match getFinalMatch( final Player p, key.util.StringTokenizer st )
{
return( new Command.Match()
{
{
match = Command.this;
}
public String getErrorString()
{
return( "" );
}
} );
}
public boolean recloneArgs()
{
return( false );
}
public static abstract class Match
{
public Commandable match = null;
public Commandable lastchance = null;
public abstract String getErrorString();
}
}