Coverage Summary for Class: RMIServer (it.polimi.ingsw.gc14.Network.RMI.Server)

Class Class, % Method, % Branch, % Line, %
RMIServer 0% (0/1) 0% (0/16) 0% (0/34) 0% (0/81)


 package it.polimi.ingsw.gc14.Network.RMI.Server;
 
 import it.polimi.ingsw.gc14.Controller.GameController;
 import it.polimi.ingsw.gc14.ErrorType;
 import it.polimi.ingsw.gc14.LimitedMap;
 import it.polimi.ingsw.gc14.Model.Game;
 import it.polimi.ingsw.gc14.Model.GamePackage.GameStages;
 import it.polimi.ingsw.gc14.Model.MiniModel;
 import it.polimi.ingsw.gc14.Network.NetworkConfig;
 import it.polimi.ingsw.gc14.Network.NetworkEvent;
 import it.polimi.ingsw.gc14.Network.NetworkEvents.*;
 import it.polimi.ingsw.gc14.Network.RMI.Common.IClientCallback;
 import it.polimi.ingsw.gc14.Network.RMI.Common.IGameServer;
 
 import java.rmi.RemoteException;
 import java.rmi.registry.LocateRegistry;
 import java.rmi.registry.Registry;
 import java.rmi.server.UnicastRemoteObject;
 import java.util.ArrayList;
 import java.util.Map;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.ConcurrentHashMap;
 
 import java.util.stream.Collectors;
 
 
 /**
  * RMI server responsible for handling remote client connections,
  * receiving player actions, and propagating game updates.
  *
  * <p>The server manages player registration, reconnection handling,
  * heartbeat monitoring, and the forwarding of client requests to the
  * shared network event queue.
  */
 public class RMIServer extends UnicastRemoteObject implements IGameServer {
     /** Hostname or IP address exposed by the RMI runtime ({@code java.rmi.server.hostname}). */
     private final String host;
     /** Server-side game controller; all model access is synchronized on this object. */
     private final GameController controller;
     /** Port on which the RMI registry is bound. */
     private final int nPort;
 
     /** username → callback */
     private final Map<String, IClientCallback> clients = new ConcurrentHashMap<>();
 
     /**
      * username → watchdog.
      * One watchdog per connected player, mirrors {@code pendingHeartbeat} / per-socket
      * HeartbeatHandler in the TCP stack.
      */
     private final Map<String, RMIHeartbeat> watchdogs = new ConcurrentHashMap<>();
     /** Shared queue to which player actions are added for sequential processing. */
     private final BlockingQueue<NetworkEvent> actionQueue;
     /** Shared map tracking each player's connection status ({@code true} = online). */
     private final LimitedMap<String, Boolean> playerList;
 
 
     /**
      * Constructs an RMI server with the required game and network components.
      *
      * @param controller  the game controller used to manage the game logic.
      * @param nPort       the port used by the RMI registry.
      * @param actionQueue the queue containing incoming network events.
      * @param playerList  the map storing the connection status of the players.
      * @param host        the hostname or IP address exposed by the RMI server.
      * @throws RemoteException if the remote object cannot be exported.
      */
     public RMIServer(GameController controller, int nPort,
                      BlockingQueue<NetworkEvent> actionQueue,
                      LimitedMap<String, Boolean> playerList,
                      String host) throws RemoteException {
         this.controller = controller;
         this.nPort = nPort;
         this.actionQueue = actionQueue;
         this.playerList = playerList;
         this.host = host;
     }
 
     // -------------------------------------------------------------------------
     // Join
     // -------------------------------------------------------------------------
 
     /**
      * {@inheritDoc}
      *
      * <p>After a successful join an {@link RMIHeartbeat} watchdog is created and
      * started for the new player — mirrors creating a {@code HeartbeatHandler} in
      * {@code TCPServer.acceptHeartbeat()}.
      *
      * @return {@code null} on success; an {@link it.polimi.ingsw.gc14.ErrorType} value on rejection.
      */
     @Override
     public ErrorType joinGame(String username, int preferredInt, IClientCallback callback)
             throws RemoteException {
         if (preferredInt < NetworkConfig.MIN_PLAYERS || preferredInt > NetworkConfig.MAX_PLAYERS)
             return ErrorType.WRONG_PLAYER_NUMBER;
 
         synchronized (controller) {
 
             if (playerList.isEmpty() &&(controller.getModel()==null || controller.getModel().getCurrentState().getGameStage().equals(GameStages.WAITING))) {
                 controller.setModel(new Game(preferredInt));
                 playerList.setLimit(preferredInt);
                 System.out.println("Game Created With: "+preferredInt+" Players");
             }
             if(controller.getModel().getCurrentState().getGameStage()!= GameStages.WAITING ) {
                 if (controller.getModel().getPlayers().stream().noneMatch(p -> p.getUserName().equals(username))|| controller.getModel().getCurrentState().getGameStage()==GameStages.ENDED) {
                     return ErrorType.GAME_ALREADY_STARTED;
                 }
             }
             else {
                 if (controller.addPlayer(username)) {
                     clients.put(username, callback);
                     playerList.put(username, true);
                     System.out.println("Accepted player: " + username);
                     startWatchdog(username);
                     System.out.println("Heartbeat connected for: " + username);
                     return null;
                 } else {
                     return ErrorType.INVALID_USERNAME;
                 }
             }
 
 
             if(!playerList.containsKey(username)){
                 clients.put(username, callback);
                 playerList.put(username, true);
                 System.out.println("(After crash)Reconnected player: " + username);
                 startWatchdog(username);
                 System.out.println("Heartbeat connected for: " + username);
                 return null;
             }
             else {
                 if(!playerList.get(username))
                 {
                     playerList.put(username, true);
                     clients.put(username, callback);
                     System.out.println("Reconnected player: " + username);
                     startWatchdog(username);
                     System.out.println("Heartbeat connected for: " + username);
                     Game game = controller.getModel();
                     callback.onGameInit(new MiniModel(
                             game.getSlotMap(), game.getOrderLogicCard(), game.getCurrentState(),
                             game.getPlayers(), game.getAvailableTotems(),
                             game.getUpperListTribeCards(), game.getLowerListTribeCards(),
                             game.getUpperListBuilding(), game.getLowerListBuilding(),
                             disconnectedUsernames(game)));
                     System.out.println("Model sent: " + username);
                     actionQueue.add(new ReconnectPlayer(username));
                     return null;
                 }
                 else
                     return ErrorType.USER_ALREADY_CONNECTED;
             }
         }
     }
 
     // -------------------------------------------------------------------------
     // Heartbeat — called by RMIClient every ~3 s
     // -------------------------------------------------------------------------
 
     /**
      * Receives a heartbeat ping from the client.
      * Mirrors the server reading {@code PING} and replying {@code PONG} in
      * {@code HeartbeatHandler.run()}.
      *
      * @param username the username of the pinging client.
      */
     @Override
     public void ping(String username) throws RemoteException {
         RMIHeartbeat wd = watchdogs.get(username);
         if (wd != null) wd.receivePing();
     }
 
     // -------------------------------------------------------------------------
     // Game model propagation — keep watchdogs in sync
     // -------------------------------------------------------------------------
 
     /**
      * Notifies connected RMI clients of a new network event.
      *
      * <p>If the event does not represent an error, it is sent to all registered clients.
      * If it represents an error, it is sent only to the client that requested the action.
      * Communication errors are logged without interrupting the notification process.
      *
      * @param action the network event to send to the clients.
      */
     public void notifyAll(NetworkEvent action){
         for (Map.Entry<String, IClientCallback> entry : clients.entrySet()) {
             if (!action.isError() || action.getUsername().equals(entry.getKey())) {
                 try{
                     entry.getValue().onAction(action);
                 }
                 catch (RemoteException e)
                 {
                     System.out.println("Remote exception: Exception during action sending attempt " + e.getMessage());
                 }
             }
         }
     }
 
     /**
      * Notifies all connected RMI clients of a new game model.
      *
      * <p>The updated mini model is sent to each registered client callback.
      * If a communication error occurs for a client, the exception is logged
      * without interrupting the notification of the remaining clients.
      *
      * @param model the updated mini model to send to the connected clients.
      */
     public void notifyAll(MiniModel model) {
         for (IClientCallback cb : clients.values()) {
             try{
                 cb.onGameInit(model);
             }
             catch (RemoteException e)
             {
                 System.out.println("Remote exception: Exception during model sending attempt " + e.getMessage());
             }
         }
     }
 
     /**
      * Requests to draw a tribe card from the upper list.
      * Creates a NetworkEvent and sends it through the network client.
      * @param playerUsername the name of the player performing the action
      * @param pos the index of the card to draw
      */
     public void drawUpperTribeCard(String playerUsername, int pos) {
         actionQueue.offer(new DrawUpperTribeCard(playerUsername,pos));
     }
 
 
     /**
      * Requests to draw a tribe card from the lower list.
      * Creates a NetworkEvent and sends it through the network client.
      * @param playerUsername the name of the player performing the action
      * @param pos the index of the card to draw
      */
     public void drawLowerTribeCard(String playerUsername,int pos) {
         actionQueue.offer(new DrawLowerTribeCard(playerUsername,pos));
     }
 
 
     /**
      * Requests to draw a building card from the upper list.
      * Creates a NetworkEvent and sends it through the network client.
      * @param playerUsername the name of the player performing the action
      * @param pos the index of the card to draw
      */
     public void drawUpperBuildingCard(String playerUsername,int pos) {
         actionQueue.offer(new DrawUpperBuildingCard(playerUsername,pos));
     }
 
 
     /**
      * Requests to draw a building card from the lower list.
      * Creates a NetworkEvent and sends it through the network client.
      * @param playerUsername the name of the player performing the action
      * @param pos the index of the card to draw
      */
     public void drawLowerBuildingCard(String playerUsername,int pos) {
         actionQueue.offer(new DrawLowerBuildingCard(playerUsername,pos));
     }
 
 
     /**
      * Requests to skip the turn.
      * This action is available only when the player cannot draw any tribe card, but still can buy some buildings.
      * @param playerUsername the name of the player performing the action
      */
     public void skipTurn(String playerUsername) {
         actionQueue.offer(new SkipTurn(playerUsername));
     }
 
 
 
     /**
      * Used to perform the slot choice action for the specified player at the specified position.
      * @param playerUsername the name of the player performing the action
      * @param pos the index of the selected slot
      */
     public void slotChoice(String playerUsername,int pos) {
         actionQueue.offer(new SlotChoice(playerUsername,pos));
     }
 
 
     /**
      * Enqueues a totem choice event for the specified player.
      *
      * @param playerUsername the username of the player choosing the totem.
      * @param totems the name of the chosen totem.
      * @throws RemoteException if the RMI call fails.
      */
     public void totemChoice(String playerUsername, String totems) throws RemoteException {
         actionQueue.offer(new TotemChoice(playerUsername,totems));
     }
 
 
     /**
      * Starts the RMI server and binds it to the configured registry.
      *
      * <p>The method configures the server hostname, creates the RMI registry
      * on the specified port, and registers this server instance under the
      * {@code RMIGameServer} name.
      */
     public void start() {
         try {
             System.setProperty("java.rmi.server.hostname", host);
             Registry registry = LocateRegistry.createRegistry(nPort);
             registry.rebind("RMIGameServer", this);
             System.out.println("RMI Server started on port: " + nPort);
         } catch (RemoteException e) {
             e.printStackTrace();
         }
     }
 
 
     /**
      * Creates and starts an {@link RMIHeartbeat} for the specified player.
      *
      * @param username the username of the player monitored by the heartbeat watchdog.
      */
     private void startWatchdog(String username) {
         RMIHeartbeat wd = new RMIHeartbeat(
                 username, playerList, clients, actionQueue);
         watchdogs.put(username, wd);
         wd.start();
     }
     /**
      * Triggers a voluntary disconnection for the specified player
      * by stopping their heartbeat watchdog.
      *
      * @param username the username of the player to disconnect.
      */
     @Override
     public void disconnectPlayer(String username) {
         RMIHeartbeat wd = watchdogs.get(username);
         if (wd != null) wd.disconnect();
     }
 
     /** Returns usernames of players currently marked as disconnected in the given game. */
     private static ArrayList<String> disconnectedUsernames(Game game) {
         return game.getDisconnectedPlayers().entrySet().stream()
                 .filter(Map.Entry::getValue)
                 .map(e -> e.getKey().getUserName())
                 .collect(Collectors.toCollection(ArrayList::new));
     }
 
 
 
 }