/* ** j###t ########## #### #### ** j###t ########## #### #### ** j###T "###L J###" ** ######P' ########## ######### ** ######k, ########## T######T ** ####~###L #### ** #### q###L ########## .##### ** #### \###L ########## #####" */ package key; import key.primitive.*; import key.collections.*; import java.net.*; import java.io.*; import java.util.*; import java.lang.*; import key.commands.Load; import key.commands.Link; import key.commands.Sync; import key.commands.Quit; import key.commands.Dump; import key.*; import key.config.BaseConfiguration; import key.config.ConnectionConfiguration; /** * Key is the root object in the hierarchy. * * This class is a little badly behaved, as far as containers * go. It starts out life as a normal container, so it can * get its defaults loaded in. These are other containers, such * as 'players' and 'groups'. * * Once these are in place, it switches to being a reference * container, meaning that everything *else* that is placed in here * is just a reference to something else. * * This would be a problem, except that this class is never * stored anywhere, it is generated everytime at bootup by * its constructor. */ public abstract class Key extends Scape { private static final long serialVersionUID = 894671100741606045L; public static final AtomicElement[] ELEMENTS = { AtomicElement.construct( Key.class, TimeStatistics.class, "loginStats", AtomicElement.PUBLIC_FIELD, "the amount of time spent by people on the program" ), AtomicElement.construct( Key.class, TimeStatistics.class, "sinceBootStats", AtomicElement.PUBLIC_FIELD, "statistics since the program was last reset" ), AtomicElement.construct( Key.class, TimeStatistics.class, "bootStats", AtomicElement.PUBLIC_FIELD, "statistics about when the program is restarted" ), }; public static final AtomicStructure STRUCTURE = new AtomicStructure( Scape.STRUCTURE, ELEMENTS ); protected static final int DEFAULT_PORT = 2809; /** Major release number */ public static final int RELEASE = 0; /** Minor version. Eg: Version release.version */ public static final int VERSION = 12; protected static Key reference = null; public static boolean DEBUG_MODE = true; protected transient LatentCache latencyCache = (LatentCache) Factory.makeAtom( LatentCache.class, "latencyCache" ); protected transient Scheduler scheduler = (Scheduler) Factory.makeAtom( Scheduler.class, "scheduler" ); protected transient Room voidRoom; // directories //protected transient File logsBasePath; //protected transient File clanLogsBasePath; protected transient Landscape locations; protected transient Container commandSets; protected transient Container groups; protected transient Container clans; protected transient Container var; protected transient BaseConfiguration config; protected transient Internet sites = null; protected transient Daemons daemons; protected transient Residents residents; protected transient Container online; protected transient Shortcuts shortcuts = (Shortcuts) Factory.makeAtom( Shortcuts.class, "shortcuts" ); // every time someone connects // this is saved and keeps connection information //public final TimeStatistics loginStats = new TimeStatistics(); public final TimeStatistics loginStats = (TimeStatistics) Factory.makeAtom( TimeStatistics.class, "loginStats" ); // this is reset at every startup //public final TimeStatistics sinceBootLoginStatistics = new TimeStatistics(); public final TimeStatistics sinceBootStats = (TimeStatistics) Factory.makeAtom( TimeStatistics.class, "sinceBootStats" ); // this is for key boots - it can tell you the amount of time // key has been online for, etc, etc //public final TimeStatistics bootStats = new TimeStatistics(); public final TimeStatistics bootStats = (TimeStatistics) Factory.makeAtom( TimeStatistics.class, "bootStats" ); /** Indicates whether the program is still in the process of booting */ private static boolean running = false; // META: change to private protected static boolean creation = false; public static final String nullString = "$"; public static final String unlinkedString = "$unlinked$ "; public static final char nullEntry = '$'; /** * The easiest way to get the top level * object in the running system. */ public static Key instance() { return( reference ); } public static boolean isRunning() { return( running ); } public AtomicStructure getDeclaredStructure() { return( STRUCTURE ); } private void init() { reference = this; Log.logsBasePath = confirmedDirectory( "logs" ); Log.clanLogsBasePath = confirmedDirectory( "logs/clans" ); } /** * Before calling start, have called "initFieldCache()", * or problems will result. */ protected void start() { if( latencyCache == null ) throw new UnexpectedResult( "initFieldCache() not called or not working" ); startDaemons(); running = true; // this should always be *last* in the system startup procedure bootStats.startConnection(); Log.bootLog( "boot completed" ); } public void setupPlayersCollection() { playergroup.setCollection( new ShortcutCollection() ); } protected Key() { super( false ); reference = this; setKey( "Key (forest)" ); stopBeingTemporary(); init(); /* latencyCache = new LatentCache(); scheduler = new Scheduler(); shortcuts = new Shortcuts(); */ Log.bootLog( "initialising default configuration..." ); commandSets = new Container( "commandSets", Type.COMMANDLIST ); try { // the 'base' commandset is the hardcoded default // that you recieve when you log in. Almost certainly, // the first thing you'll want to do is make a group // for the core administrators and move all these // cool commands (like shutdown) to there. CommandList base = new CommandList( "base" ); commandSets.add( base ); base.add( Factory.makeAtom( Load.class ) ); base.add( Factory.makeAtom( Link.class ) ); base.add( Factory.makeAtom( Sync.class ) ); base.add( Factory.makeAtom( Quit.class ) ); base.add( Factory.makeAtom( Dump.class ) ); groups = (Container) Factory.makeAtom( Container.class, "groups" ); groups.setConstraint( Type.GROUP ); clans = (Container) Factory.makeAtom( Container.class, "clans" ); clans.setConstraint( Type.CLAN ); var = (Container) Factory.makeAtom( Container.class, "var" ); var.setConstraint( Type.CONTAINER ); config = (BaseConfiguration) Factory.makeAtom( BaseConfiguration.class ); config.add( Factory.makeAtom( ConnectionConfiguration.class ) ); sites = (Internet) Factory.makeAtom( Internet.class ); online = (Container) Factory.makeAtom( Container.class, "online" ); } catch( NonUniqueKeyException e ) { Log.error( "couldn't initialise defaults", e ); } catch( BadKeyException e ) { Log.error( "couldn't initialise defaults", e ); } try { TextParagraph logo = new TextParagraph( TextParagraph.CENTERALIGNED, "\n\n" + " j###t ########## #### ####\n" + " j###t ########## #### ####\n" + " j###T \"###L J###\"\n" + "######P' ########## #########\n" + "######k, ########## T######T\n" + "####~###L ####\n" + "#### q###L ########## .#####\n" + "#### \\###L ########## #####\"\n\n" ); Screen hs = (Screen) Factory.makeAtom( Screen.class, "logo" ); hs.text = logo; online.add( hs ); online.add( Factory.makeAtom( HelpContainer.class ) ); online.add( Factory.makeAtom( StringSet.class, "reservedNames" ) ); } catch( NonUniqueKeyException e ) { Log.error( "Couldn't initialise default online", e ); } catch( BadKeyException e ) { Log.error( "Couldn't initialise default online", e ); } daemons = (Daemons) Factory.makeAtom( Daemons.class ); TelnetConnectPort connectSocket = null; try { connectSocket = (TelnetConnectPort) Factory.makeAtom( TelnetConnectPort.class, "telnetPort" ); daemons.add( connectSocket ); } catch( NonUniqueKeyException e ) { Log.error( "Couldn't initialise default daemons", e ); } catch( BadKeyException e ) { Log.error( "Couldn't initialise default daemons", e ); } residents = (Residents) Factory.makeAtom( Residents.class, "players" ); residents.setConstraint( Type.PLAYER ); locations = (Landscape) Factory.makeAtom( Landscape.class, "landscape" ); try { voidRoom = (Room) Factory.makeAtom( Room.class, "void" ); locations.add( voidRoom ); } catch( NonUniqueKeyException e ) { Log.error( "Couldn't initialise default landscape", e ); } catch( BadKeyException e ) { Log.error( "Couldn't initialise default landscape", e ); } // add, try { add( shortcuts ); add( daemons ); add( residents ); add( locations ); add( commandSets ); add( groups ); add( clans ); add( config ); add( sites ); add( online ); add( var ); add( latencyCache ); add( scheduler ); addTransient( new Me() ); addTransient( new Here() ); addTransient( new Context() ); addTransient( new FriendsRef() ); addTransient( new InformRef() ); addTransient( new ClanRef() ); locations.addTopLevel( shortcuts ); groups.addTopLevel( shortcuts ); clans.addTopLevel( shortcuts ); residents.addTopLevel( shortcuts ); } catch( NonUniqueKeyException e ) { Log.fatal( this, "couldn't add required global symbol" + e.toString() ); } catch( BadKeyException e ) { Log.fatal( this, "couldn't add required global symbol: " + e.toString() ); } } private void addTransient( TransitAtom ta ) throws NonUniqueKeyException, BadKeyException { add( ta ); shortcuts.add( ta ); } private void readObject( ObjectInputStream ois ) throws IOException { init(); try { ois.defaultReadObject(); } catch( ClassNotFoundException e ) { throw new UnexpectedResult( e.toString() ); } sinceBootStats.reset(); } /** * For speed, Key caches the lookups for many of it's contained * atoms. This method initialises these caches after Key has been * loaded. * <P> * All the addReference()s are required to prevent these atoms from * being swapped out while we have pointers to them (which would invalidate * the pointers and screw up the whole system). */ protected void initFieldCache() { latencyCache = (LatentCache) getElement( "latencyCache" ); latencyCache.addReference( this ); scheduler = (Scheduler) getElement( "scheduler" ); scheduler.addReference( this ); shortcuts = (Shortcuts) getElement( "shortcuts" ); shortcuts.addReference( this ); commandSets = (Container) getElement( "commandSets" ); commandSets.addReference( this ); groups = (Container) getElement( "groups" ); groups.addReference( this ); clans = (Container) getElement( "clans" ); clans.addReference( this ); config = (BaseConfiguration) getElement( "config" ); ((Container)config).addReference( this ); sites = (Internet) getElement( "sites" ); sites.addReference( this ); online = (Container) getElement( "online" ); online.addReference( this ); daemons = (Daemons) getElement( "daemons" ); daemons.addReference( this ); residents = (Residents) getElement( "players" ); residents.addReference( this ); locations = (Landscape) getElement( "landscape" ); locations.addReference( this ); voidRoom = (Room) locations.getElement( "void" ); voidRoom.addReference( this ); var = (Container) getElement( "var" ); var.addReference( this ); // the latency cache is needed before we're really // running. if( !latencyCache.isAlive() ) { Log.bootLog( "starting " + latencyCache.getName() ); latencyCache.start(); } } protected void startDaemons() { for( Enumeration e = daemons.elements(); e.hasMoreElements(); ) { Animate daemon = (Animate) e.nextElement(); if( !daemon.isAlive() ) { Log.bootLog( "starting " + ((Atom)daemon).getName() ); daemon.start(); } } // okay, lets start the system up if( !latencyCache.isAlive() ) { Log.bootLog( "starting " + latencyCache.getName() ); latencyCache.start(); } if( !scheduler.isAlive() ) { Log.bootLog( "starting " + scheduler.getName() ); scheduler.start(); } } /** * Returns the landscape referenced by the supplied * id. eg 'realm.hill.top' */ public Landscape getLandscape( String match ) { //Object a = locations.getSymbol( match ); Object a = locations.getElement( match ); if( a instanceof Landscape ) return( (Landscape) a ); else return( null ); } // the rank a new player gets put in public Rank getInitialRank() { return( null ); } public Room getConnectRoom( Player p ) { return( voidRoom ); } public Container getOnline() { return( online ); } public Container getClans() { return( clans ); } /** * Override for each talker */ public abstract Realm getDefaultRealm(); public BaseConfiguration getConfig() { return( config ); } /** * overrides default so that timeStatistics can be updated */ public void linkPlayer( Player p ) throws NonUniqueKeyException,BadKeyException { super.linkPlayer( p ); loginStats.startConnection(); sinceBootStats.startConnection(); } /** * overrides default so that timeStatistics can be updated */ public void unlinkPlayer( Player p ) throws NonUniqueKeyException,BadKeyException { super.unlinkPlayer( p ); loginStats.endConnection(); sinceBootStats.endConnection(); } public void addTopLevel( Container addTo ) { throw new UnexpectedResult( "trying to addTopLevel Key to something" ); } String getId_imp() { return( "" ); } String getId_kickoff() { return( "/" ); } /** * This routine is only _ever_ meant to be called from * the scheduler. */ public synchronized void shutdown() { Player p = Player.getCurrent(); if( p != null && p.connected() ) { p.send( "You struggle in vain against the pattern. To destroy something, you must be outside of it." ); return; } running = false; bootStats.endConnection(); Log.bootLog( "shutdown initiated, disconnecting players" ); // disconnect all the players for( Enumeration e = players(); e.hasMoreElements(); ) { p = (Player) e.nextElement(); try { p.disconnect(); } catch( Exception t ) { Log.error( "during shutdown disconnect", t ); } } Log.bootLog( "stopping daemons" ); // stop all running daemons for( Enumeration e = daemons.elements(); e.hasMoreElements(); ) { Animate daemon = (Animate) e.nextElement(); if( daemon.isAlive() ) daemon.stop(); } if( latencyCache.isAlive() ) latencyCache.stop(); Log.bootLog( "writing database" ); Log.shutdown(); try { sync(); } catch( Exception e ) { e.printStackTrace(); } finally { System.exit( 0 ); } } public static final LatentCache getLatencyCache() { return( reference.latencyCache ); } public static final Scheduler getScheduler() { return( reference.scheduler ); } public static final File getLogsBasePath() { return( Log.logsBasePath ); } public static final Internet getInternet() { return( reference.sites ); } public static final Container shortcuts() { return( reference.shortcuts ); } /** * Use this to build a var container for a particular name */ protected Container ensureAndGetVarEntry( String name ) { Container c = (Container) var.getElement( name ); if( c != null ) return c; c = new Container( false, name, new NoKeyCollection() ); try { var.addInternal( c.getThis() ); } catch( Exception e ) { throw new UnexpectedResult( e.toString() + " while adding unmatched container" ); } return( c ); } /** * Updates the system disk files so that * the changes to non-saved objects will * be saved. (eg, which commands are * where, etc) */ public synchronized void sync() { File temp = new File( "database" ); Factory.storeObject( Registry.instance, temp ); } public Residents getResidents() { return( residents ); } public File confirmedFile( File basePath, String name ) { File opened; opened = new File( basePath, name ); if( opened.exists() ) { if( !opened.canRead() ) Log.fatal( this, "'" + opened.getPath() + "' is not readable" ); if( !opened.canWrite() ) Log.fatal( this, "'" + opened.getPath() + "' is not writeable" ); } else if( !creation ) Log.fatal( this, "'" + opened.getAbsolutePath() + "' does not exist. Use the 'creation' command line argument to allow Key to automatically generate paths." ); return( opened ); } public static File confirmedDirectory( String path ) { File opened; opened = new File( path ); if( !opened.exists() ) { if( creation ) { if( opened.mkdir() ) Log.bootLog( "'" + path + "' created" ); else Log.fatal( "Key", "'" + path + "' does not exist and cannot be created" ); } else Log.fatal( "Key", "'" + opened.getAbsolutePath() + "' does not exist. Use the 'creation' command line argument to allow Key to automatically generate paths." ); } if( !opened.canRead() ) Log.fatal( "Key", "'" + path + "' is not readable" ); if( !opened.canWrite() ) Log.fatal( "Key", "'" + path + "' is not writeable" ); return( opened ); } // you may not change this message. public static final String copyright = "\n\nKey: \"building virtual worlds\"\n" + "Copyright (C) 1997-1998 Paul Mclachlan, Jason Crane and George Wright.\n" ; }