Clover coverage report -
Coverage timestamp: Sun Oct 12 2003 22:57:21 PDT
file stats: LOC: 718   Methods: 27
NCLOC: 341   Classes: 5
 
 Source file Conditionals Statements Methods TOTAL
World.java 0% 0% 0% 0%
coverage
 1   
 /*
 2   
 
 3   
 VRMoo Server - Virtual Reality Object Oriented MUD Server
 4   
 Copyright (C) 2001 - 2003  VRMoo Development Team
 5   
 
 6   
 
 7   
 This program is free software; you can redistribute it and/or modify
 8   
 it under the terms of the GNU General Public License as published by
 9   
 the Free Software Foundation; either version 2 of the License, or
 10   
 (at your option) any later version.
 11   
 
 12   
 This program is distributed in the hope that it will be useful,
 13   
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 14   
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15   
 GNU General Public License for more details.
 16   
 
 17   
 You should have received a copy of the GNU General Public License
 18   
 along with this program; if not, write to the Free Software
 19   
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 20   
 
 21   
 
 22   
 For information about VRMoo and its authors, please visit the website:
 23   
 http://www.vrmoo.org/
 24   
 
 25   
 */
 26   
 
 27   
 package org.vrmoo.server.world;
 28   
 
 29   
 import java.io.BufferedReader;
 30   
 import java.io.File;
 31   
 import java.io.PrintWriter;
 32   
 import java.util.ArrayList;
 33   
 import java.util.HashMap;
 34   
 import java.util.List;
 35   
 import java.util.Map;
 36   
 
 37   
 import org.vrmoo.common.data.AppearanceData;
 38   
 import org.vrmoo.common.data.ObjectData;
 39   
 import org.vrmoo.common.data.VisibleObject;
 40   
 import org.vrmoo.common.exception.VRMooException;
 41   
 import org.vrmoo.common.exception.VRMooParseException;
 42   
 import org.vrmoo.common.objectbroker.AppearanceDataBroker;
 43   
 import org.vrmoo.common.objectbroker.ObjectDataBroker;
 44   
 import org.vrmoo.common.objectbroker.VisibleObjectBroker;
 45   
 import org.vrmoo.common.util.CommandHandler;
 46   
 import org.vrmoo.common.util.CommandProcessor;
 47   
 import org.vrmoo.common.util.FileUtility;
 48   
 import org.vrmoo.common.util.ParserUtility;
 49   
 import org.vrmoo.common.util.Base64Utility;
 50   
 
 51   
 import org.vrmoo.server.client.AllClients;
 52   
 import org.vrmoo.server.client.ClientHandler;
 53   
 import org.vrmoo.server.data.TextureManager;
 54   
 
 55   
 /**
 56   
  * This class contains the functionality for dealing with an interactive 3D
 57   
  * world that can be explored and altered by VRMoo Clients.
 58   
  *
 59   
  *@author    Jeff Weston
 60   
  */
 61   
 public class World extends CommandHandler implements CommandProcessor {
 62   
     /**
 63   
      * The root directory where all world data is saved.
 64   
      */
 65   
     private static final String WORLD_DATA_DIR = "data/worlds";
 66   
 
 67   
     /**
 68   
      * The prefix used for world commands.
 69   
      */
 70   
     private static final String WORLD_COMMAND_PREFIX = "world:";
 71   
 
 72   
     /**
 73   
      * Holds the name of the world.
 74   
      */
 75   
     private String name;
 76   
 
 77   
     /**
 78   
      * Holds the directory location of the world data.
 79   
      */
 80   
     private String worldDataDirectory;
 81   
 
 82   
     /**
 83   
      * The directory where avatar data is stored
 84   
      */
 85   
     private String avatarDirectory;
 86   
 
 87   
     /**
 88   
      * Holds the list of clients currently connected to this world.
 89   
      */
 90   
     private List clients;
 91   
 
 92   
     /**
 93   
      * Holds the map of clients that are currently connecting to this world
 94   
      * paired up with the state information for those connecting clients.
 95   
      */
 96   
     private Map connectingClients;
 97   
 
 98   
     /**
 99   
      * The texture manager for this world.
 100   
      */
 101   
     private TextureManager textureManager;
 102   
 
 103   
     /**
 104   
      * The appearance data broker for this world.
 105   
      */
 106   
     private AppearanceDataBroker appearanceBroker;
 107   
 
 108   
     /**
 109   
      * The object data broker for this world.
 110   
      */
 111   
     private ObjectDataBroker objectBroker;
 112   
 
 113   
     /**
 114   
      * The visible object broker for this world.
 115   
      */
 116   
     private VisibleObjectBroker visibleObjectBroker;
 117   
 
 118   
 
 119   
     /**
 120   
      * This default constructor creates a minimal 3D world.
 121   
      *
 122   
      *@param name                the name for the world
 123   
      *@exception VRMooException  Description of the Exception
 124   
      *@throws VRMooException     for any errors while creating the world
 125   
      */
 126  0
     public World( String name )
 127   
         throws VRMooException {
 128  0
         this.name = name;
 129  0
         worldDataDirectory = WORLD_DATA_DIR + "/" + name;
 130  0
         avatarDirectory = worldDataDirectory + "/avatars";
 131  0
         File dataDir = new File( worldDataDirectory );
 132  0
         FileUtility.ensureDirectoryExists( dataDir );
 133   
 
 134  0
         textureManager = new TextureManager( this );
 135   
 
 136  0
         appearanceBroker = new AppearanceDataBroker();
 137  0
         appearanceBroker.addCommandHandler( this );
 138  0
         addCommandProcessor( appearanceBroker );
 139   
 
 140  0
         objectBroker = new ObjectDataBroker();
 141  0
         objectBroker.addCommandHandler( this );
 142  0
         addCommandProcessor( objectBroker );
 143   
 
 144  0
         visibleObjectBroker = new VisibleObjectBroker();
 145  0
         visibleObjectBroker.addCommandHandler( this );
 146  0
         addCommandProcessor( visibleObjectBroker );
 147   
 
 148  0
         addCommandProcessor( new NewLocationCommandProcessor() );
 149  0
         addCommandProcessor( new SendCommandCommandProcessor() );
 150  0
         addCommandProcessor( new AvatarCommandProcessor() );
 151   
 
 152  0
         clients = new ArrayList();
 153  0
         connectingClients = new HashMap();
 154   
 
 155  0
         loadWorldData();
 156   
     }
 157   
 
 158   
 
 159   
     /**
 160   
      * Returns the command prefix used to recognize commands that this processor
 161   
      * can handle.
 162   
      *
 163   
      *@return   the command prefix
 164   
      */
 165  0
     public String getCommandPrefix() {
 166  0
         return WORLD_COMMAND_PREFIX;
 167   
     }
 168   
 
 169   
 
 170   
     /**
 171   
      * Process a command matching the command prefix established by the
 172   
      * <code>getCommandPrefix()</code> method.
 173   
      *
 174   
      *@param command          the command to process
 175   
      *@param extraData        extra data that isn't part of the command but may
 176   
      *      be needed for dealing with the command (such as the ID of the client
 177   
      *      that sent the command)
 178   
      *@throws VRMooException  for any errors while processing the command
 179   
      */
 180  0
     public synchronized void processCommand( String command, String extraData )
 181   
         throws VRMooException {
 182  0
         String worldCommand = command.substring(
 183   
                 WORLD_COMMAND_PREFIX.length() ).trim();
 184   
 
 185  0
         handleCommand( worldCommand, extraData );
 186   
     }
 187   
 
 188   
 
 189   
     /**
 190   
      * Get the name of the world.
 191   
      *
 192   
      *@return   the name of the world
 193   
      */
 194  0
     public String getName() {
 195  0
         return name;
 196   
     }
 197   
 
 198   
 
 199   
     /**
 200   
      * Get the data directory of the world.
 201   
      *
 202   
      *@return   the data directory of the world
 203   
      */
 204  0
     public String getWorldDataDirectory() {
 205  0
         return worldDataDirectory;
 206   
     }
 207   
 
 208   
 
 209   
     /**
 210   
      * Connect a client to this world.
 211   
      *
 212   
      *@param client  the <code>ClientHandler</code> to connect
 213   
      */
 214  0
     public synchronized void connectClient( ClientHandler client ) {
 215  0
         if ( ( !clients.contains( client ) ) &&
 216   
                 ( !connectingClients.containsKey( client ) ) ) {
 217   
             // Register this world to receive world commands from the client.
 218  0
             client.addCommandProcessor( this );
 219   
 
 220   
             // Add the client to the list of connecting clients.
 221  0
             connectingClients.put( client, new ConnectingClientState() );
 222   
 
 223   
             // Start the texture initialization procedure.
 224  0
             textureManager.newClientConnected( client );
 225   
         }
 226   
     }
 227   
 
 228   
 
 229   
     /**
 230   
      * Indicate that the specified client has finished the texture
 231   
      * initialization procedure. If this is the final step for connecting the
 232   
      * client to the world, then connect the client to the world.
 233   
      *
 234   
      *@param client           the <code>ClientHandler</code> that has finished
 235   
      *      the texture initialization procedure
 236   
      *@throws VRMooException  for any errors while processing the command
 237   
      */
 238  0
     public synchronized void clientFinishedTextures( ClientHandler client )
 239   
         throws VRMooException {
 240  0
         if ( connectingClients.containsKey( client ) ) {
 241  0
             ConnectingClientState clientState =
 242   
                     ( ConnectingClientState ) connectingClients.get( client );
 243  0
             clientState.texturesFinished();
 244  0
             if ( clientState.clientReady() ) {
 245  0
                 clientConnected( client );
 246   
             }
 247   
         }
 248   
     }
 249   
 
 250   
 
 251   
     /**
 252   
      * Disconnect a client from this world.
 253   
      *
 254   
      *@param client  the <code>ClientHandler</code> to disconnect
 255   
      */
 256  0
     public synchronized void disconnectClient( ClientHandler client ) {
 257  0
         if ( clients.contains( client ) ) {
 258  0
             client.removeCommandProcessor( this );
 259  0
             clients.remove( client );
 260  0
             broadcast( "disconnect: " + client.getClientID() );
 261   
         }
 262   
 
 263  0
         if ( connectingClients.containsKey( client ) ) {
 264  0
             client.removeCommandProcessor( this );
 265  0
             connectingClients.remove( client );
 266   
         }
 267   
         //Remove avatar data for this client
 268  0
         File f = FileUtility.getImageFile(avatarDirectory + "/client" +
 269   
             client.getClientID());
 270  0
         if (f != null) {
 271  0
             f.delete();
 272   
         }
 273   
     }
 274   
 
 275   
 
 276   
     /**
 277   
      * Sends a line of text to all connected clients.
 278   
      *
 279   
      *@param line  the line of text
 280   
      */
 281  0
     public synchronized void broadcast( String line ) {
 282  0
         for ( int i = 0; i < clients.size(); i++ ) {
 283  0
             ClientHandler client = ( ClientHandler ) clients.get( i );
 284  0
             client.writeLine( line );
 285   
         }
 286   
     }
 287   
 
 288   
 
 289   
     /**
 290   
      * Save all of the data used by this world out to disk.
 291   
      */
 292  0
     public synchronized void saveWorldData() {
 293  0
         System.out.println( "Saving world data..." + name );
 294  0
         saveAppearanceData();
 295  0
         saveObjectData();
 296  0
         saveVisibleObjects();
 297   
     }
 298   
 
 299   
 
 300   
     /**
 301   
      * Load all of the data used by this world from disk.
 302   
      */
 303  0
     private void loadWorldData() {
 304  0
         System.out.println( "Loading world data..." + name );
 305  0
         loadAppearanceData();
 306  0
         loadObjectData();
 307  0
         loadVisibleObjects();
 308   
     }
 309   
 
 310   
 
 311   
     /**
 312   
      * Save all of the appearances used by this world out to disk.
 313   
      */
 314  0
     private void saveAppearanceData( )
 315   
     {
 316  0
         try
 317   
         {
 318  0
             PrintWriter writer = FileUtility.getPrintWriter(
 319   
                     worldDataDirectory + "/appearances.dat" );
 320  0
             String[] appearanceNames =
 321   
                     appearanceBroker.getAppearanceDataNames();
 322   
 
 323  0
             for ( int i = 0; i < appearanceNames.length; i++ ) {
 324  0
                 AppearanceData appearance = appearanceBroker.getAppearanceData(
 325   
                         appearanceNames[i] );
 326  0
                 writer.println( appearance.toEncodedString() );
 327   
             }
 328  0
             writer.close();
 329   
         } catch ( Exception e ) {
 330  0
             e.printStackTrace();
 331   
         }
 332   
     }
 333   
 
 334   
 
 335   
     /**
 336   
      * Load all of the appearances used by this world from disk.
 337   
      */
 338  0
     private void loadAppearanceData( )
 339   
     {
 340  0
         try
 341   
         {
 342  0
             BufferedReader reader = FileUtility.getBufferedReader(
 343   
                 worldDataDirectory + "/appearances.dat" );
 344   
 
 345  0
             String line = reader.readLine();
 346   
 
 347  0
             while ( line != null ) {
 348  0
                 AppearanceData appearance = new AppearanceData( line );
 349  0
                 appearanceBroker.setAppearanceData( appearance );
 350  0
                 line = reader.readLine();
 351   
             }
 352  0
             reader.close();
 353   
         } catch ( Exception e ) {
 354  0
             e.printStackTrace();
 355   
         }
 356   
     }
 357   
 
 358   
 
 359   
     /**
 360   
      * Save all of the object definitions used by this world out to disk.
 361   
      */
 362  0
     private void saveObjectData( )
 363   
     {
 364  0
         try
 365   
         {
 366  0
             PrintWriter writer = FileUtility.getPrintWriter(
 367   
                     worldDataDirectory + "/objects.dat" );
 368  0
             String[] objectNames = objectBroker.getObjectDataNames();
 369   
 
 370  0
             for ( int i = 0; i < objectNames.length; i++ ) {
 371  0
                 ObjectData object = objectBroker.getObjectData(
 372   
                         objectNames[i] );
 373  0
                 writer.println( object.toEncodedString() );
 374   
             }
 375  0
             writer.close();
 376   
         } catch ( Exception e ) {
 377  0
             e.printStackTrace();
 378   
         }
 379   
     }
 380   
 
 381   
 
 382   
     /**
 383   
      * Load all of the object definitions used by this world from disk.
 384   
      */
 385  0
     private void loadObjectData( )
 386   
     {
 387  0
         try
 388   
         {
 389  0
             BufferedReader reader = FileUtility.getBufferedReader(
 390   
                 worldDataDirectory + "/objects.dat" );
 391   
 
 392  0
             String line = reader.readLine();
 393   
 
 394  0
             while ( line != null ) {
 395  0
                 ObjectData object = new ObjectData( line );
 396  0
                 objectBroker.setObjectData( object );
 397  0
                 line = reader.readLine();
 398   
             }
 399  0
             reader.close();
 400   
         } catch ( Exception e ) {
 401  0
             e.printStackTrace();
 402   
         }
 403   
     }
 404   
 
 405   
 
 406   
     /**
 407   
      * Save all of the visible objects used by this world out to disk.
 408   
      */
 409  0
     private void saveVisibleObjects( )
 410   
     {
 411  0
         try
 412   
         {
 413  0
             PrintWriter writer = FileUtility.getPrintWriter(
 414   
                     worldDataDirectory + "/visibleObjects.dat" );
 415  0
             int[] visibleObjectNames =
 416   
                     visibleObjectBroker.getVisibleObjectNames();
 417   
 
 418  0
             for ( int i = 0; i < visibleObjectNames.length; i++ ) {
 419  0
                 VisibleObject visibleObject =
 420   
                         visibleObjectBroker.getVisibleObject(
 421   
                         visibleObjectNames[i] );
 422  0
                 writer.println( visibleObject.toEncodedString() );
 423   
             }
 424  0
             writer.close();
 425   
         } catch ( Exception e ) {
 426  0
             e.printStackTrace();
 427   
         }
 428   
     }
 429   
 
 430   
 
 431   
     /**
 432   
      * Load all of the visible objects used by this world from disk.
 433   
      */
 434  0
     private void loadVisibleObjects( )
 435   
     {
 436  0
         try
 437   
         {
 438  0
             BufferedReader reader = FileUtility.getBufferedReader(
 439   
                 worldDataDirectory + "/visibleObjects.dat" );
 440   
 
 441  0
             String line = reader.readLine();
 442   
 
 443  0
             while ( line != null ) {
 444  0
                 VisibleObject visibleObject = new VisibleObject( line );
 445  0
                 visibleObjectBroker.addVisibleObject( visibleObject );
 446  0
                 line = reader.readLine();
 447   
             }
 448  0
             reader.close();
 449   
         } catch ( Exception e ) {
 450  0
             e.printStackTrace();
 451   
         }
 452   
     }
 453   
 
 454   
     /**
 455   
      * Indicates that the specified client is now actually connected.
 456   
      * Move the client from the "connecting" state to the "connected"
 457   
      * state.
 458   
      *
 459   
      * @param client   the <code>ClientHandler</code> that is now connected
 460   
      *
 461   
      *@throws VRMooException  for any errors while processing the command
 462   
      */
 463  0
     private void clientConnected( ClientHandler client )
 464   
         throws VRMooException {
 465  0
         if ( ( connectingClients.containsKey( client ) ) &&
 466   
                 ( !clients.contains( client ) ) ) {
 467   
             // Add the client to the object brokers.
 468  0
             appearanceBroker.clientConnected( client.getClientID() );
 469  0
             objectBroker.clientConnected( client.getClientID() );
 470  0
             visibleObjectBroker.clientConnected( client.getClientID() );
 471   
 
 472   
             // Tell all of the clients that a new client has connected.
 473  0
             broadcast( "connect: " + client.getClientID() );
 474   
 
 475   
             // Remove the client from the list of connecting clients.
 476  0
             connectingClients.remove( client );
 477   
 
 478   
             // Add the client to the list of connected clients.
 479  0
             clients.add( client );
 480   
 
 481   
             // Send the new client a list of all the connected clients.
 482  0
             sendClientList( client );
 483   
         }
 484   
     }
 485   
 
 486   
 
 487   
     /**
 488   
      * Send a list of all the connected clients to the specified client.
 489   
      *
 490   
      *@param clientHandler  the client to send the list to
 491   
      */
 492  0
     private void sendClientList( ClientHandler clientHandler ) {
 493  0
         for ( int i = 0; i < clients.size(); i++ ) {
 494  0
             ClientHandler client = ( ClientHandler ) clients.get( i );
 495  0
             clientHandler.writeLine( "connect: " + client.getClientID() );
 496   
         }
 497   
     }
 498   
 
 499   
 
 500   
     /**
 501   
      * Private inner class for tracking the state of a connecting client.
 502   
      *
 503   
      *@author    dusty
 504   
      */
 505   
     private class ConnectingClientState {
 506   
         /**
 507   
          * This flag determines if the client has finished the texture
 508   
          * initialization procedure. This flag must be set to true in order for
 509   
          * the client to finish connecting to the world.
 510   
          */
 511   
         private boolean texturesComplete = false;
 512   
 
 513   
 
 514   
         /**
 515   
          * This method indicates that the client has finished the texture
 516   
          * initialization procedure.
 517   
          */
 518  0
         public void texturesFinished() {
 519  0
             texturesComplete = true;
 520   
         }
 521   
 
 522   
 
 523   
         /**
 524   
          * This method determines if the client is ready to be connected. It
 525   
          * will only return true if all of the flags determining client
 526   
          * readiness have also been set to true.
 527   
          *
 528   
          *@return   true if the client is ready to connect, false otherwise
 529   
          */
 530  0
         public boolean clientReady() {
 531  0
             return texturesComplete;
 532   
         }
 533   
     }
 534   
 
 535   
 
 536   
     /**
 537   
      * Private inner class implementing the <code>CommandProcessor</code>
 538   
      * interface for dealing with the "newLocation" command sent from the
 539   
      * client.
 540   
      *
 541   
      *@author    dusty
 542   
      */
 543   
     private class NewLocationCommandProcessor implements CommandProcessor {
 544   
         /**
 545   
          * The prefix for the command handled by this processor.
 546   
          */
 547   
         private static final String PREFIX = "newLocation:";
 548   
 
 549   
 
 550   
         /**
 551   
          * Returns the command prefix used to recognize commands that this
 552   
          * processor can handle.
 553   
          *
 554   
          *@return   the command prefix
 555   
          */
 556  0
         public String getCommandPrefix() {
 557  0
             return PREFIX;
 558   
         }
 559   
 
 560   
 
 561   
         /**
 562   
          * Process a command matching the command prefix established by the
 563   
          * <code>getCommandPrefix()</code> method.
 564   
          *
 565   
          *@param command               the command to process
 566   
          *@param extraData             extra data that isn't part of the command
 567   
          *      but may be needed for dealing with the command (such as the ID
 568   
          *      of the client that sent the command)
 569   
          *@throws VRMooParseException  for any parsing errors while processing
 570   
          *      the command
 571   
          */
 572  0
         public void processCommand( String command, String extraData )
 573   
             throws VRMooParseException {
 574  0
             int clientId = ParserUtility.parseInt( extraData );
 575  0
             ClientHandler client = AllClients.getClientByID( clientId );
 576   
 
 577  0
             if ( clients.contains( client ) ) {
 578  0
                 String location =
 579   
                         command.substring( PREFIX.length() ).trim();
 580  0
                 broadcast( "newClientLocation: [" +
 581   
                         clientId + "] " + location );
 582   
             }
 583   
         }
 584   
     }
 585   
 
 586   
 
 587   
     /**
 588   
      * Private inner class implementing the <code>CommandProcessor</code>
 589   
      * interface for dealing with the "avatar" command sent from the client.
 590   
      *
 591   
      *@author    dusty
 592   
      */
 593   
     private class AvatarCommandProcessor implements CommandProcessor {
 594   
         /**
 595   
          * The prefix for the command handled by this processor.
 596   
          */
 597   
         private static final String PREFIX = "avatar:";
 598   
 
 599   
 
 600   
         /**
 601   
          * Returns the command prefix used to recognize commands that this
 602   
          * processor can handle.
 603   
          *
 604   
          *@return   the command prefix
 605   
          */
 606  0
         public String getCommandPrefix() {
 607  0
             return PREFIX;
 608   
         }
 609   
 
 610   
 
 611   
         /**
 612   
          * Process a command matching the command prefix established by the
 613   
          * <code>getCommandPrefix()</code> method.
 614   
          *
 615   
          *@param command               the command to process
 616   
          *@param extraData             extra data that isn't part of the command
 617   
          *      but may be needed for dealing with the command (such as the ID
 618   
          *      of the client that sent the command)
 619   
          *@throws VRMooParseException  for any parsing errors while processing
 620   
          *      the command
 621   
          */
 622  0
         public void processCommand( String command, String extraData )
 623   
             throws VRMooParseException {
 624  0
             int clientId = ParserUtility.parseInt( extraData );
 625  0
             ClientHandler client = AllClients.getClientByID( clientId );
 626  0
             String avatarData =
 627   
                         command.substring( PREFIX.length() ).trim();
 628   
 
 629  0
             if ( clients.contains( client ) ||
 630   
                     ( connectingClients.containsKey( client ) ) ) {
 631   
                 //Ensure that no files for this client exist
 632  0
                 File oldAv = FileUtility.getImageFile(avatarDirectory +
 633   
                     "/client" + clientId);
 634  0
                 if (oldAv != null) {
 635  0
                     oldAv.delete();
 636   
                 }
 637  0
                 int delimiter = avatarData.indexOf( "," );
 638   
                 //ext is the extension (png, jpg, gif) sent from client
 639  0
                 String ext = avatarData.substring( 0, delimiter ).trim( );
 640  0
                 String base64Data =
 641   
                         avatarData.substring( delimiter + 1 ).trim( );
 642  0
                 File avFile = new File(
 643   
                     avatarDirectory + "/client" + clientId + "." + ext);
 644   
                 //Save client avatar
 645  0
                 byte[ ] data = Base64Utility.toBinary( base64Data );
 646  0
                 try
 647   
                 {
 648  0
                     FileUtility.ensureDirectoryExists(
 649   
                             new File(avatarDirectory));
 650  0
                     avFile.createNewFile();
 651  0
                     FileUtility.writeFile( avFile, data );
 652   
                 }
 653   
                 catch ( Exception e )
 654   
                 {
 655  0
                     e.printStackTrace( );
 656   
                 }
 657  0
                 broadcast( "world: newAvatar: [" +
 658   
                            clientId + "] " + avatarData );
 659   
             }
 660   
         }
 661   
     }
 662   
 
 663   
 
 664   
     /**
 665   
      * Private inner class implementing the <code>CommandProcessor</code>
 666   
      * interface for dealing with the "sendCommand" command sent from the
 667   
      * <code>AppearanceDataBroker</code>.
 668   
      *
 669   
      *@author    dusty
 670   
      */
 671   
     private class SendCommandCommandProcessor implements CommandProcessor {
 672   
         /**
 673   
          * The prefix for the command handled by this processor.
 674   
          */
 675   
         private static final String PREFIX = "sendCommand:";
 676   
 
 677   
 
 678   
         /**
 679   
          * Returns the command prefix used to recognize commands that this
 680   
          * processor can handle.
 681   
          *
 682   
          *@return   the command prefix
 683   
          */
 684  0
         public String getCommandPrefix() {
 685  0
             return PREFIX;
 686   
         }
 687   
 
 688   
 
 689   
         /**
 690   
          * Process a command matching the command prefix established by the
 691   
          * <code>getCommandPrefix()</code> method.
 692   
          *
 693   
          *@param command          the command to process
 694   
          *@param extraData        extra data that isn't part of the command but
 695   
          *      may be needed for dealing with the command (such as the ID of
 696   
          *      the client that sent the command)
 697   
          *@throws VRMooException  for any errors while processing the command
 698   
          */
 699  0
         public void processCommand( String command, String extraData )
 700   
             throws VRMooException {
 701  0
             String sendCommand = command.substring( PREFIX.length() ).trim();
 702   
 
 703  0
             if ( extraData == null ) {
 704  0
                 broadcast( sendCommand );
 705   
             }
 706   
             else {
 707  0
                 int[] clients = ParserUtility.parseIntArray( extraData );
 708   
 
 709  0
                 for ( int i = 0; i < clients.length; i++ ) {
 710  0
                     ClientHandler clientHandler =
 711   
                             AllClients.getClientByID( clients[i] );
 712  0
                     clientHandler.writeLine( sendCommand );
 713   
                 }
 714   
             }
 715   
         }
 716   
     }
 717   
 }
 718