public class LobbyConfig extends PlaceConfig
{
    /**
     * A default constructor used when unserializing.
     */
    public LobbyConfig ()
    {
    }

    /**
     * Creates the config for a new lobby that will match-make games with
     * the specified configuration.
     */
    public LobbyConfig (int gameId, GameDefinition gamedef)
    {
        _gameId = gameId;
        _gamedef = gamedef;
    }

    // documentation inherited
    @Override
    public PlaceController createController ()
    {
        return new LobbyController();
    }

    // documentation inherited
    @Override
    public String getManagerClassName ()
    {
        return "com.threerings.toybox.lobby.server.LobbyManager";
    }

    /**
     * Returns this game's unique identifier.
     */
    public int getGameId ()
    {
        return _gameId;
    }

    /**
     * Returns the definition of the game we're matchmaking in this lobby.
     */
    public GameDefinition getGameDefinition ()
    {
        return _gamedef;
    }

    // documentation inherited
    @Override
    protected void toString (StringBuilder buf)
    {
        super.toString(buf);
        if (buf.length() > 1) {
            buf.append(", ");
        }
        buf.append("gamedef=").append(_gamedef);
    }

    /** The unique id for the game we'll be matchmaking. */
    protected int _gameId;

    /** The definition for the game we'll be matchmaking. */
    protected GameDefinition _gamedef;
} public class LobbyObject extends PlaceObject
     implements TableLobbyObject
{
    // AUTO-GENERATED: FIELDS START
    /** The field name of the <code>name</code> field. */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public static final String NAME = "name";

    /** The field name of the <code>tableSet</code> field. */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public static final String TABLE_SET = "tableSet";

    /** The field name of the <code>tableService</code> field. */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public static final String TABLE_SERVICE = "tableService";
    // AUTO-GENERATED: FIELDS END

    /** The name of the game we're match-making in this lobby. */
    public String name;

    /** A set containing all of the tables being managed by this lobby.  This may be empty if we're
     * not using tables to match-make. */
    public DSet<Table> tableSet = new DSet<Table>();

    /** Used to communicate to the table manager. */
    public TableMarshaller tableService;

    // from interface TableLobbyObject
    public DSet<Table> getTables ()
    {
        return tableSet;
    }

    // from interface TableLobbyObject
    public TableMarshaller getTableService ()
    {
        return tableService;
    }

    // from interface TableLobbyObject
    public void addToTables (Table table)
    {
        addToTableSet(table);
    }

    // from interface TableLobbyObject
    public void updateTables (Table table)
    {
        updateTableSet(table);
    }

    // from interface TableLobbyObject
    public void removeFromTables (Comparable<?> key)
    {
        removeFromTableSet(key);
    }

    /**
     * Counts up the occupants of this lobby and of all games hosted from this lobby.
     */
    public int countOccupants (RootDObjectManager omgr)
    {
        // add the occupants of the room
        ArrayIntSet occids = new ArrayIntSet();
        for (int ii = 0; ii < occupants.size(); ii++) {
            occids.add(occupants.get(ii));
        }
        for (Table table : tableSet) {
            if (table.gameOid > 0) {
                // for now we can directly reference the game object
                Object obj = omgr.getObject(table.gameOid);
                if (obj instanceof PlaceObject) {
                    PlaceObject plobj = (PlaceObject)obj;
                    for (int ii = 0; ii < plobj.occupants.size(); ii++) {
                        occids.add(plobj.occupants.get(ii));
                    }
                }
            }
        }
        // remove any zeros that got in from a zero bodyOid
        occids.remove(0);
        return occids.size();
    }

    // AUTO-GENERATED: METHODS START
    /**
     * Requests that the <code>name</code> field be set to the
     * specified value. The local value will be updated immediately and an
     * event will be propagated through the system to notify all listeners
     * that the attribute did change. Proxied copies of this object (on
     * clients) will apply the value change when they received the
     * attribute changed notification.
     */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public void setName (String value)
    {
        String ovalue = this.name;
        requestAttributeChange(
            NAME, value, ovalue);
        this.name = value;
    }

    /**
     * Requests that the specified entry be added to the
     * <code>tableSet</code> set. The set will not change until the event is
     * actually propagated through the system.
     */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public void addToTableSet (Table elem)
    {
        requestEntryAdd(TABLE_SET, tableSet, elem);
    }

    /**
     * Requests that the entry matching the supplied key be removed from
     * the <code>tableSet</code> set. The set will not change until the
     * event is actually propagated through the system.
     */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public void removeFromTableSet (Comparable<?> key)
    {
        requestEntryRemove(TABLE_SET, tableSet, key);
    }

    /**
     * Requests that the specified entry be updated in the
     * <code>tableSet</code> set. The set will not change until the event is
     * actually propagated through the system.
     */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public void updateTableSet (Table elem)
    {
        requestEntryUpdate(TABLE_SET, tableSet, elem);
    }

    /**
     * Requests that the <code>tableSet</code> field be set to the
     * specified value. Generally one only adds, updates and removes
     * entries of a distributed set, but certain situations call for a
     * complete replacement of the set value. The local value will be
     * updated immediately and an event will be propagated through the
     * system to notify all listeners that the attribute did
     * change. Proxied copies of this object (on clients) will apply the
     * value change when they received the attribute changed notification.
     */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public void setTableSet (DSet<Table> value)
    {
        requestAttributeChange(TABLE_SET, value, this.tableSet);
        DSet<Table> clone = (value == null) ? null : value.clone();
        this.tableSet = clone;
    }

    /**
     * Requests that the <code>tableService</code> field be set to the
     * specified value. The local value will be updated immediately and an
     * event will be propagated through the system to notify all listeners
     * that the attribute did change. Proxied copies of this object (on
     * clients) will apply the value change when they received the
     * attribute changed notification.
     */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public void setTableService (TableMarshaller value)
    {
        TableMarshaller ovalue = this.tableService;
        requestAttributeChange(
            TABLE_SERVICE, value, ovalue);
        this.tableService = value;
    }
    // AUTO-GENERATED: METHODS END
} public class LobbyManager extends PlaceManager
{
    /**
     * Provides this lobby manager with a reference to its game and the ToyBox
     * manager with whom it should work.
     */
    public void init (ToyBoxManager toymgr, GameRecord game)
    {
        _toymgr = toymgr;
        _game = game;

        // this happens after we've started up so we configure our lobby
        // object with the game name now
        _lobobj.setName(_game.name);

        // we don't have a game reference the first time our idle unload
        // interval is scheduled, so cancel it if we discover that we're in
        // testing mode and don't ever want to unload
        if (game.gameId == -1) {
            cancelShutdowner();
        }

        // if we're using the table services to match-make, create a table
        // manager
        GameDefinition gdef = _lconfig.getGameDefinition();
        if (gdef.match instanceof TableMatchConfig) {
            _tablemgr = new ToyBoxTableManager(_omgr, _invmgr, _registry, _toymgr, this);
        }
    }

    /**
     * Returns a reference to the game associated with this lobby.
     */
    public GameRecord getGame ()
    {
        return _game;
    }

    // documentation inherited
    @Override
    protected void didInit ()
    {
        super.didInit();

        _lconfig = (LobbyConfig)_config;
    }

    // documentation inherited
    @Override
    protected void didStartup ()
    {
        super.didStartup();

        _lobobj = (LobbyObject)_plobj;
        _lobobj.addListener(_emptyListener);
    }

    // documentation inherited
    @Override
    protected void placeBecameEmpty ()
    {
        // we don't want to do the standard "became empty" processing
        // until all of our tables are also empty
        if (_lobobj.tableSet.size() == 0) {
            super.placeBecameEmpty();
        }
    }

    // documentation inherited
    @Override
    protected long idleUnloadPeriod ()
    {
        // unload our lobbies very quickly after they become empty; unless
        // we're in testing mode in which case we want to never unload
        return (_game != null && _game.gameId == -1) ? 0L : 15 * 1000L;
    }

    // documentation inherited
    @Override
    protected void didShutdown ()
    {
        super.didShutdown();

        // unregister with our toybox manager
        _toymgr.lobbyDidShutdown(_game);

        // shutdown our table manager
        if (_tablemgr != null) {
            _tablemgr.shutdown();
        }
    }

    // documentation inherited
    @Override
    protected PlaceObject createPlaceObject ()
    {
        return new LobbyObject();
    }

    /** Listens for tables shutting down and reports us as empty if there
     * are no people in the lobby and our last table went away. */
    protected SetAdapter<Table> _emptyListener = new SetAdapter<Table>() {
        @Override
        public void entryRemoved (EntryRemovedEvent<Table> event) {
            if (event.getName().equals(LobbyObject.TABLE_SET)) {
                if (_lobobj.tableSet.size() == 0 &&
                    _lobobj.occupants.size() == 0) {
                    placeBecameEmpty();
                }
            }
        }
    };

    /** The ToyBox manager with whom we operate. */
    protected ToyBoxManager _toymgr;

    /** The game record associated with our game. */
    protected GameRecord _game;

    /** A casted reference to our lobby config. */
    protected LobbyConfig _lconfig;

    /** A casted reference to our lobby object. */
    protected LobbyObject _lobobj;

    /** Our table manager, which is only created if we're using tables to
     * match-make in this lobby. */
    protected TableManager _tablemgr;
} @Singleton public class ToyBoxRepository extends DepotRepository
    implements ToyBoxManager.GameRepository
{
    /**
     * Constructs a new repository with the specified persistence context.
     */
    @Inject public ToyBoxRepository (PersistenceContext ctx)
    {
        super(ctx);
    }

    /**
     * Loads up all of the games in the repository.
     */
    public List<GameRecord> loadGames ()
    {
        return findAll(GameRecord.class, new Where(ready()));
    }

    /**
     * Loads up all of the games in the repository with the specified category.
     */
    public List<GameRecord> loadGames (String category)
    {
        return findAll(GameRecord.class, new Where(GameRecord.CATEGORY.eq(category).and(ready())));
    }

    /**
     * Loads the specified number of the most popular games as determined by the number of minutes
     * played in those games in the current and previous week.
     */
    public List<GameRecord> loadPopularGames (final int count)
        throws PersistenceException
    {
        class GameData implements Comparable<GameData> {
            public int gameId;
            public int playtime;
            public int compareTo (GameData other) {
                return other.playtime - playtime;
            }
        }

        Map<Integer,GameData> data = Maps.newHashMap();
        for (Date when : new Date[] { getWeek(0), getWeek(-1) }) {
            for (PlaytimeRecord prec : findAll(
                     PlaytimeRecord.class, new Where(PlaytimeRecord.PERIOD.eq(when)))) {
                GameData gd = data.get(prec.gameId);
                if (gd == null) {
                    data.put(prec.gameId, gd = new GameData());
                    gd.gameId = prec.gameId;
                }
                gd.playtime += prec.playtime;
            }
        }

        final Map<Integer,GameRecord> games = Maps.newHashMap();
        for (GameRecord grec : findAll(
                 GameRecord.class, new Where(GameRecord.GAME_ID.in(data.keySet())))) {
            games.put(grec.gameId, grec);
        }

        List<GameData> popular = Lists.newArrayList(data.values());
        Collections.sort(popular);
        return Lists.transform(popular.subList(0, Math.min(popular.size(), count)),
                               new Function<GameData,GameRecord>() {
                                   public GameRecord apply (GameData data) {
                                       return games.get(data.gameId);
                                   }
                               });
    }

    /**
     * Loads the specified number of the most recently created games in the system.
     */
    public List<GameRecord> loadRecentlyAdded (final int count)
    {
        return findAll(GameRecord.class, new Where(ready()),
                       OrderBy.descending(GameRecord.CREATED), new Limit(0, count));
    }

    /**
     * Loads the specified number of the most recently updated games in the system.
     */
    public List<GameRecord> loadRecentlyUpdated (final int count)
    {
        return findAll(GameRecord.class, new Where(ready()),
                       OrderBy.descending(GameRecord.LAST_UPDATED), new Limit(0, count));
    }

    /**
     * Loads information on a single game from the repository. Returns null if no game exists with
     * the specifed id.
     */
    public GameRecord loadGame (int gameId)
        throws PersistenceException
    {
        return load(GameRecord.getKey(gameId));
    }

    /**
     * Inserts the supplied game into the repository. {@link GameRecord#gameId} will be filled in
     * by this method with the game's newly assigned unique identifier.
     */
    public void insertGame (final GameRecord game)
        throws PersistenceException
    {
        insert(game);
    }

    /**
     * Updates the supplied game in the repository. Returns true if a matching row was found and
     * updated, false if no rows matched.
     */
    public boolean updateGame (final GameRecord game)
        throws PersistenceException
    {
        int mod = update(game);
        switch (mod) {
        case 0: return false;
        case 1: return true;
        default:
            log.warning("updateGame() modified more than one row?!", "game", game, "modified", mod);
            return true; // something was updated!
        }
    }

    /**
     * Increments the number of minutes played for the specified game in the current week.
     */
    public void incrementPlaytime (int gameId, int minutes)
        throws PersistenceException
    {
        Date when = getWeek(0);
        // first try updating
        int mods = updatePartial(PlaytimeRecord.class,
                                 new Where(PlaytimeRecord.GAME_ID.eq(gameId).and(
                                               PlaytimeRecord.PERIOD.eq(when))), null,
                                 PlaytimeRecord.PLAYTIME, PlaytimeRecord.PLAYTIME.plus(minutes));
        if (mods == 0) {
            // if that failed to modify anything, insert
            PlaytimeRecord pr = new PlaytimeRecord();
            pr.gameId = gameId;
            pr.period = when;
            pr.playtime = minutes;
            insert(pr);
        }
    }

    /**
     * Clears out the players online counts. Called when the server starts up.
     */
    public void clearOnlineCounts ()
        throws PersistenceException
    {
        deleteAll(OnlineRecord.class, new Where(Exps.value(true)));
    }

    /**
     * Updates the number of players online for the specified game.
     */
    public void updateOnlineCount (int gameId, int players)
        throws PersistenceException
    {
        OnlineRecord record = new OnlineRecord();
        record.gameId = gameId;
        record.players = players;
        store(record);
    }

    /**
     * Looks up the number of players online for the specified game.
     */
    public int getOnlineCount (int gameId)
        throws PersistenceException
    {
        OnlineRecord record = load(OnlineRecord.getKey(gameId));
        return (record == null) ? 0 : record.players;
    }

    /**
     * Returns information on how many players are in games.
     */
    public List<Tuple<String,OnlineRecord>> getOnlineCounts ()
        throws PersistenceException
    {
        // first obtain the game ids of the top N most frequently played games
        List<OnlineRecord> orecs = findAll(
            OnlineRecord.class, new Where(OnlineRecord.PLAYERS.greaterThan(0)));

        Set<Integer> gameIds = Sets.newHashSet();
        for (OnlineRecord orec : orecs) {
            gameIds.add(orec.gameId);
        }
        final Map<Integer, String> gameNames = Maps.newHashMap();
        for (GameRecord grec : findAll(
                 GameRecord.class, new Where(GameRecord.GAME_ID.in(gameIds)))) {
            gameNames.put(grec.gameId, grec.name);
        }

        return Lists.transform(orecs, new Function<OnlineRecord, Tuple<String,OnlineRecord>>() {
            public Tuple<String,OnlineRecord> apply (OnlineRecord orec) {
                return Tuple.newTuple(gameNames.get(orec.gameId), orec);
            }
        });
    }

    /** Returns a {@link Date} instance configured to the beginning of the current (or current +
     * offset) week. */
    protected static Date getWeek (int offset)
    {
        Calendar now = Calendar.getInstance();
        int year = now.get(Calendar.YEAR);
        int week = now.get(Calendar.WEEK_OF_YEAR);
        now.clear();
        now.set(Calendar.YEAR, year);
        now.set(Calendar.WEEK_OF_YEAR, week);
        now.add(Calendar.WEEK_OF_YEAR, offset);
        return new Date(now.getTimeInMillis());
    }

    @Override
    protected void getManagedRecords (Set<Class<? extends PersistentRecord>> classes)
    {
        classes.add(GameRecord.class);
        classes.add(OnlineRecord.class);
        classes.add(PlaytimeRecord.class);
    }

    protected static FluentExp<Boolean> ready ()
    {
        return GameRecord.STATUS.eq(GameRecord.Status.READY.toString());
    }
} public class GameDefinition implements Streamable
{
    /** A string identifier for the game. */
    public String ident;

    /** The class name of the <code>GameController</code> derivation that we use to bootstrap on
     * the client. */
    public String controller;

    /** The class name of the <code>GameManager</code> derivation that we use to manage the game on
     * the server. */
    public String manager;

    /** The MD5 digest of the game media file. */
    public String digest;

    /** The configuration of the match-making mechanism. */
    public MatchConfig match;

    /** Parameters used to configure the game itself. */
    public Parameter[] params;

    /**
     * Provides the path to this game's media (a jar file).
     *
     * @param gameId the unique id of the game provided when this game definition was registered
     * with the system, or -1 if we're running in test mode.
     */
    public String getMediaPath (int gameId)
    {
        return (gameId == -1) ? ident + ".jar" : ident + "-" + gameId + ".jar";
    }

    /**
     * Returns true if a single player can play this game (possibly against AI opponents), or if
     * opponents are needed.
     */
    public boolean isSinglePlayerPlayable ()
    {
        // maybe it's just single player no problem
        int minPlayers = 2;
        if (match != null) {
            minPlayers = match.getMinimumPlayers();
            if (minPlayers <= 1) {
                return true;
            }
        }

        // or maybe it has AIs
        int aiCount = 0;
        for (Parameter param : params) {
            if (param instanceof AIParameter) {
                aiCount = ((AIParameter)param).maximum;
            }
        }
        return (minPlayers - aiCount) <= 1;
    }

    /** Called when parsing a game definition from XML. */
    @ActionScript(omit=true)
    public void setParams (List<Parameter> list)
    {
        params = list.toArray(new Parameter[list.size()]);
    }

    /** Generates a string representation of this instance. */
    @Override
    public String toString ()
    {
        return StringUtil.fieldsToString(this);
    }
} public class ToyBoxGameConfig extends GameConfig
{
    /** Our configuration parameters. These will be seeded with the defaults from the game
     * definition and then configured by the player in the lobby. */
    public StreamableHashMap<String,Object> params = new StreamableHashMap<String,Object>();

    /** A zero argument constructor used when unserializing. */
    public ToyBoxGameConfig ()
    {
    }

    /** Constructs a game config based on the supplied game definition. */
    public ToyBoxGameConfig (int gameId, GameDefinition gameDef)
    {
        Preconditions.checkNotNull(gameDef, "Missing GameDefinition");

        _gameId = gameId;
        _gameDef = gameDef;

        if (gameDef.params != null) {
            // set the default values for our parameters
            for (Parameter param : gameDef.params) {
                params.put(param.ident, param.getDefaultValue());
            }
        }
    }

    /**
     * Returns the non-changing metadata that defines this game.
     */
    public GameDefinition getGameDefinition ()
    {
        return _gameDef;
    }

    /** Returns true if this is a party game, false otherwise. */
    public boolean isPartyGame ()
    {
        return getMatchType() == PARTY;
    }

    @Override // from GameConfig
    public int getGameId ()
    {
        return _gameId;
    }

    @Override // from GameConfig
    public String getGameIdent ()
    {
        return _gameDef.ident;
    }

    @Override // from GameConfig
    public int getMatchType ()
    {
        return _gameDef.match.getMatchType();
    }

    @Override // from GameConfig
    public GameConfigurator createConfigurator ()
    {
        return new ToyBoxGameConfigurator();
    }

    @Override // from PlaceConfig
    public PlaceController createController ()
    {
        String ctrl = getGameDefinition().controller;
        if (ctrl == null) {
            throw new IllegalStateException("Game definition missing controller [gdef=" +
                getGameDefinition() + "]");
        }
        try {
            return (PlaceController) Class.forName(ctrl).newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override // from PlaceConfig
    public String getManagerClassName ()
    {
        return _gameDef.manager;
    }

    /** Our game's unique id. */
    protected int _gameId;

    /** Our game definition. */
    protected GameDefinition _gameDef;
} @Generated(value={"com.threerings.presents.tools.GenServiceTask"},
           comments="Derived from ToyBoxService.java.")
public class ToyBoxMarshaller extends InvocationMarshaller<ClientObject>
    implements ToyBoxService
{
    /** The method id used to dispatch {@link #getLobbyOid} requests. */
    public static final int GET_LOBBY_OID = 1;

    // from interface ToyBoxService
    public void getLobbyOid (int arg1, InvocationService.ResultListener arg2)
    {
        InvocationMarshaller.ResultMarshaller listener2 = new InvocationMarshaller.ResultMarshaller();
        listener2.listener = arg2;
        sendRequest(GET_LOBBY_OID, new Object[] {
            Integer.valueOf(arg1), listener2
        });
    }
} public enum Status { UNKNOWN, PENDING, READY, DISABLED } @Entity(name="GAMES")
public class GameRecord extends PersistentRecord
{
    // AUTO-GENERATED: FIELDS START
    public static final Class<GameRecord> _R = GameRecord.class;
    public static final ColumnExp<Integer> GAME_ID = colexp(_R, "gameId");
    public static final ColumnExp<String> CATEGORY = colexp(_R, "category");
    public static final ColumnExp<String> NAME = colexp(_R, "name");
    public static final ColumnExp<Integer> MAINTAINER_ID = colexp(_R, "maintainerId");
    public static final ColumnExp<String> STATUS = colexp(_R, "status");
    public static final ColumnExp<String> HOST = colexp(_R, "host");
    public static final ColumnExp<String> DEFINITION = colexp(_R, "definition");
    public static final ColumnExp<String> DIGEST = colexp(_R, "digest");
    public static final ColumnExp<String> DESCRIPTION = colexp(_R, "description");
    public static final ColumnExp<String> INSTRUCTIONS = colexp(_R, "instructions");
    public static final ColumnExp<String> CREDITS = colexp(_R, "credits");
    public static final ColumnExp<Date> CREATED = colexp(_R, "created");
    public static final ColumnExp<Date> LAST_UPDATED = colexp(_R, "lastUpdated");
    // AUTO-GENERATED: FIELDS END

    public static final int SCHEMA_VERSION = 1;

    /** Defines the possible values for {@link #status}. */
    public enum Status { UNKNOWN, PENDING, READY, DISABLED }

    /** A unique integer identifier for this game. */
    @Id @Column(name="GAME_ID") @GeneratedValue(strategy=GenerationType.IDENTITY)
    public int gameId;

    /** A short string indicating the category of this game. */
    @Column(name="CATEGORY")
    public String category;

    /** The human readable name of this game. */
    @Column(name="NAME")
    public String name;

    /** The user id of the maintainer of this game. */
    @Column(name="MAINTAINER_ID") @Index
    public int maintainerId;

    /** The status of the game. We can't use the enumeration directly here as this class is
     * persisted and JORA doesn't (and can't be made to) automagically handle enums. */
    @Column(name="STATUS")
    public String status;

    /** The server on which this game is hosted. */
    @Column(name="HOST")
    public String host;

    /** The XML game definition associated with this version. */
    @Column(name="DEFINITION", length=4096)
    public String definition;

    /** The MD5 digest of the game jar file. */
    @Column(name="DIGEST")
    public String digest;

    /** A brief description of the game. */
    @Column(name="DESCRIPTION", length=16384)
    public String description;

    /** Brief instructions on how to play the game. */
    @Column(name="INSTRUCTIONS", length=8192)
    public String instructions;

    /** Credits and license information for the game. */
    @Column(name="CREDITS", length=4096)
    public String credits;

    /** The date on which the game was created. */
    @Column(name="CREATED")
    public Date created;

    /** The date on which the jar file was last updated. */
    @Column(name="LAST_UPDATED")
    public Date lastUpdated;

    /** Returns the status of this game. */
    public Status getStatus ()
    {
        return Status.valueOf(status);
    }

    /** Updates the status of this game. */
    public void setStatus (Status status)
    {
        this.status = status.toString();
    }

    /**
     * Parses this game's definition and returns
     */
    public GameDefinition parseGameDefinition ()
        throws InvocationException
    {
        if (_parser == null) {
            _parser = createParser();
        }

        try {
            GameDefinition gamedef;
            synchronized (_parser) {
                gamedef = _parser.parseGame(new StringReader(definition));
            }

            // fill in things that only we know
            gamedef.digest = digest;

            return gamedef;

        } catch (Exception e) {
//             log.warning("Failed to parse game definition [game=" + gameId + "]", e);
            throw (InvocationException)new InvocationException(
                ToyBoxCodes.ERR_MALFORMED_GAMEDEF).initCause(e);
        }
    }

    /**
     * Returns a brief description of this game.
     */
    public String which ()
    {
        return name + " (" + gameId + ")";
    }

    /**
     * Provides a string representation of this instance.
     */
    @Override
    public String toString ()
    {
        return StringUtil.fieldsToString(this);
    }

    /**
     * Creates the parser we'll use to turn our text configuration (probably XML) into a game
     * definition.
     */
    protected GameParser createParser ()
    {
        return new GameParser();
    }

    /** Used to parse our game definitions. */
    protected static GameParser _parser;

    // AUTO-GENERATED: METHODS START
    /**
     * Create and return a primary {@link Key} to identify a {@link GameRecord}
     * with the supplied key values.
     */
    public static Key<GameRecord> getKey (int gameId)
    {
        return newKey(_R, gameId);
    }

    /** Register the key fields in an order matching the getKey() factory. */
    static { registerKeyFields(GAME_ID); }
    // AUTO-GENERATED: METHODS END
} public class ToyBoxClassLoader extends URLClassLoader
{
    public ToyBoxClassLoader (URL[] sources)
    {
        super(sources, ToyBoxClassLoader.class.getClassLoader());
        _sources = sources;
        _lastModified = computeLastModified();
    }

    /**
     * Returns true if none of the jar files referenced by this class
     * loader have changed since it was first created, false otherwise.
     */
    public boolean isUpToDate ()
    {
        long[] curModified = computeLastModified();
        for (int ii = 0; ii < curModified.length; ii++) {
            if (curModified[ii] > _lastModified[ii]) {
                log.info(_sources[ii] + " has changed.");
                return false;
            }
        }
        return true;
    }

    /** Looks up the last modified time for all of our source jar files. */
    protected long[] computeLastModified ()
    {
        long[] stamps = new long[_sources.length];
        for (int ii = 0; ii < _sources.length; ii++) {
            URL source = _sources[ii];
            // these should all be file URLs but we'll be safe
            if (!source.getProtocol().equals("file")) {
                log.warning("Can't check up-to-dateness of '" + source + "'.");
                continue;
            }
            stamps[ii] = new File(source.getPath()).lastModified();
        }
        return stamps;
    }

    /** The URLs from which we get our jar files. */
    protected URL[] _sources;

    /** The last modified times of our jar files at the time we were
     * created. */
    protected long[] _lastModified;
} public class ToyBoxUtil
{
    /**
     * Creates a class loader with restricted permissions that loads classes from the game jar and
     * libraries specified by the supplied game definition. Those jar files will be assumed to live
     * relative to the specified root directory.
     */
    public static ToyBoxClassLoader createClassLoader (
        File root, int gameId, GameDefinition gamedef)
    {
        ArrayList<URL> ulist = Lists.newArrayList();
        String path = "";
        try {
            // add the game jar file
            path = "file:" + root + "/" + gamedef.getMediaPath(gameId);
            ulist.add(new URL(path));

        } catch (Exception e) {
            log.warning("Failed to create URL for class loader", "root", root, "path", path, e);
        }

        return new ToyBoxClassLoader(ulist.toArray(new URL[ulist.size()]));
    }
} public static Logger log = Logger.getLogger("com.threerings.toybox"); public class ToyBoxCodes implements InvocationCodes
{
    /** Defines our invocation services group. */
    public static final String TOYBOX_GROUP = "toybox";

    /** Defines the general ToyBox translation message bundle.*/
    public static final String TOYBOX_MSGS = "toybox";

    /** An error constant returned by the {@link ToyBoxService}. */
    public static final String ERR_NO_SUCH_GAME =
        MessageUtil.qualify(TOYBOX_MSGS, "e.no_such_game");

    /** An error constant indicating a malformed game definition. */
    public static final String ERR_MALFORMED_GAMEDEF =
        MessageUtil.qualify(TOYBOX_MSGS, "e.malformed_gamedef");
}
import java.io.File;
import java.io.FileReader;
import java.security.MessageDigest;
import java.util.Map;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.samskivert.io.PersistenceException;
import com.samskivert.io.StreamUtil;
import com.samskivert.jdbc.WriteOnlyUnit;
import com.samskivert.util.IntIntMap;
import com.samskivert.util.Interval;
import com.samskivert.util.Invoker;
import com.samskivert.util.ResultListenerList;
import com.threerings.getdown.data.Resource;
import com.threerings.presents.annotation.MainInvoker;
import com.threerings.presents.client.InvocationService.ResultListener;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.server.InvocationException;
import com.threerings.presents.server.InvocationManager;
import com.threerings.presents.server.PresentsDObjectMgr;
import com.threerings.presents.util.PersistingUnit;
import com.threerings.presents.util.ResultAdapter;
import com.threerings.crowd.data.PlaceConfig;
import com.threerings.crowd.server.PlaceManager;
import com.threerings.crowd.server.PlaceRegistry;
import com.threerings.parlor.game.data.GameConfig;
import com.threerings.parlor.game.server.GameManager;
import com.threerings.parlor.game.server.GameManagerDelegate;
import com.threerings.toybox.lobby.data.LobbyConfig;
import com.threerings.toybox.lobby.data.LobbyObject;
import com.threerings.toybox.lobby.server.LobbyManager;
import com.threerings.toybox.server.persist.ToyBoxRepository;
import com.threerings.toybox.data.GameDefinition;
import com.threerings.toybox.data.ToyBoxGameConfig;
import com.threerings.toybox.data.ToyBoxMarshaller;
import com.threerings.toybox.server.persist.GameRecord.Status;
import com.threerings.toybox.server.persist.GameRecord;
import com.threerings.toybox.util.ToyBoxClassLoader;
import com.threerings.toybox.util.ToyBoxUtil;
import static com.threerings.toybox.Log.log;
import static com.threerings.toybox.data.ToyBoxCodes.*;
//
// ToyBox library - framework for matchmaking networked games
// Copyright (C) 2005-2012 Three Rings Design, Inc., All Rights Reserved
// http://github.com/threerings/game-gardens
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.toybox.server;













/**
 * Manages the server side of the ToyBox services.
 */
@Singleton
public class ToyBoxManager
    implements ToyBoxProvider
{
    /**
     * Provides access to {@link GameRecord} info for the ToyBox manager.
     */
    public interface GameRepository
    {
        /** Loads the persistent data for a game. */
        public GameRecord loadGame (int gameId)
            throws PersistenceException;

        /** Records playtime to a game's persistent record. */
        public void incrementPlaytime (int gameId, int minutes)
            throws PersistenceException;

        /** Updates the number of players online for a game. */
        public void updateOnlineCount (int gameId, int players)
            throws PersistenceException;
    }

    @Inject public ToyBoxManager (InvocationManager invmgr, ToyBoxConfig config,
                                  PresentsDObjectMgr omgr) {
        // register ourselves as providing the toybox service
        invmgr.registerProvider(this, ToyBoxMarshaller.class, TOYBOX_GROUP);
        _config = config;
        _omgr = omgr;

        // periodically write our occupancy information to the database
        _popval = new Interval(_omgr) {
            @Override
            public void expired () {
                publishOccupancy();
            }
        };
        _popval.schedule(60 * 1000L, true);

        log.info("ToyBoxManager ready [rsrcdir=" + _config.getResourceDir() + "].");
    }

    /**
     * Prepares the toybox manager for operation in development mode where it only hosts the lobby
     * for a single game, which we will create immediately rather than on-demand.
     */
    public void setDevelopmentMode (File gameConfig)
        throws PersistenceException
    {
        // create a fake game record for this game and resolve its lobby
        GameRecord game = new GameRecord();
        GameDefinition gamedef = null;
        try {
            game.gameId = -1;
            game.name = "test";
            game.maintainerId = 1;
            game.setStatus(Status.READY);
            game.definition = StreamUtil.toString(new FileReader(gameConfig));

            // compute the digests of all the files
            gamedef = game.parseGameDefinition();
            File jar = new File(_config.getResourceDir(), gamedef.getMediaPath(game.gameId));
            log.info("Reading " + jar + "...");
            MessageDigest md = MessageDigest.getInstance("MD5");
            game.digest = Resource.computeDigest(jar, md, null);

        } catch (Exception e) {
            log.warning("Failed to load game config [path=" + gameConfig + "].", e);
            return;
        }

        try {
            resolveLobby(game, null);
        } catch (InvocationException ie) {
            log.warning("Failed to resolve lobby [game=" + game + "].", ie);
        }
    }

    /**
     * Returns the custom class loader that should be used for the
     * specified place.
     */
    public ClassLoader getClassLoader (PlaceConfig config)
    {
        if (config instanceof ToyBoxGameConfig) {
            ToyBoxGameConfig tconfig = (ToyBoxGameConfig)config;
            String ident = tconfig.getManagerClassName();
            ToyBoxClassLoader loader = _loaders.get(ident);