Coverage Summary for Class: ServerLauncher (it.polimi.ingsw.gc14)

Class Class, % Method, % Branch, % Line, %
ServerLauncher 0% (0/1) 0% (0/8) 0% (0/24) 0% (0/71)


 package it.polimi.ingsw.gc14;
 
 import it.polimi.ingsw.gc14.Controller.GameController;
 import it.polimi.ingsw.gc14.Model.Game;
 import it.polimi.ingsw.gc14.Model.MiniModel;
 import it.polimi.ingsw.gc14.Model.Player;
 import it.polimi.ingsw.gc14.Network.CompositeClientBroadcaster;
 import it.polimi.ingsw.gc14.Network.NetworkEvent;
 import it.polimi.ingsw.gc14.Network.RMI.Server.RMIServer;
 import it.polimi.ingsw.gc14.Network.NetworkConfig;
 import it.polimi.ingsw.gc14.Network.TCP.Server.TCPServer;
 
 import java.net.*;
 import java.rmi.RemoteException;
 import java.util.*;
 import java.util.concurrent.*;
 import java.util.stream.Collectors;
 
 /**
  * Main server entry point.
  *
  * <p>Responsibilities of this class are intentionally limited to wiring:
  * it creates all components, connects them together, restores a previously
  * saved game if one exists, and starts the event-processing loop and the
  * network servers.
  *
  * <p>All game-logic and event-routing decisions are delegated to
  * {@link GameEventProcessor}; persistence is delegated to {@link SaveManager}.
  *
  * <p>The original {@code ServerLauncher} class is preserved and untouched.
  * This class is a clean replacement that uses the new component structure.
  */
 public class ServerLauncher {
 
     /** Drives the main game-event loop. */
     private final GameEventProcessor eventProcessor;
 
     /**
      * Constructs a {@code ServerLauncherTest} and wires all components together.
      *
      * @param actionQueue    the shared event queue.
      * @param gameController the server-side game controller.
      * @param playerList     the shared player-status map.
      * @param broadcaster    the broadcaster used to reach all connected clients.
      * @param saveManager    the save manager used to persist game state.
      */
     public ServerLauncher(
             BlockingQueue<NetworkEvent> actionQueue,
             GameController gameController,
             LimitedMap<String, Boolean> playerList,
             CompositeClientBroadcaster broadcaster,
             SaveManager saveManager) {
         this.eventProcessor = new GameEventProcessor(
                 actionQueue, gameController, playerList, broadcaster, saveManager);
     }
 
 
     /**
      * Initializes all server components, wires them together, restores a saved
      * game if available, and starts the event-processing loop and both network
      * servers.
      *
      * @param args command-line arguments (unused).
      * @throws RemoteException if the RMI server cannot be created.
      */
     static void main(String[] args) throws RemoteException {
         LimitedMap<String, Boolean> playerList  = new LimitedMap<>(5, () -> {});
         BlockingQueue<NetworkEvent>  actionQueue = new LinkedBlockingQueue<>();
         GameController               gameController = new GameController();
 
         String ip;
         try {
             ip = chooseNetworkInterface(new Scanner(System.in));
             System.out.println(ip);
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
         System.setProperty("java.rmi.server.hostname", ip);
 
         RMIServer        rmiServer   = new RMIServer(gameController, NetworkConfig.RMI_PORT, actionQueue, playerList, ip);
         TCPServer        tcpServer   = new TCPServer(gameController, NetworkConfig.TCP_PORT, NetworkConfig.HEARTBEAT_PORT, actionQueue, playerList);
         CompositeClientBroadcaster broadcaster = new CompositeClientBroadcaster(rmiServer, tcpServer);
         SaveManager       saveManager = new SaveManager(ServerLauncher.class);
 
         restoreGameIfSaved(gameController, playerList, saveManager);
 
         ServerLauncher launcher = new ServerLauncher(
                 actionQueue, gameController, playerList, broadcaster, saveManager);
 
         // When all required players have joined, broadcast the initial MiniModel.
         playerList.setAction(() -> new Thread(() -> {
             MiniModel miniModel;
             synchronized (gameController) {
                 Game game = gameController.getModel();
                 miniModel = new MiniModel(
                         game.getSlotMap(), game.getOrderLogicCard(), game.getCurrentState(),
                         game.getPlayers(), game.getAvailableTotems(),
                         game.getUpperListTribeCards(), game.getLowerListTribeCards(),
                         game.getUpperListBuilding(),   game.getLowerListBuilding(),
                         game.getDisconnectedPlayers().entrySet().stream()
                                 .filter(Map.Entry::getValue)
                                 .map(e -> e.getKey().getUserName())
                                 .collect(Collectors.toCollection(ArrayList::new))
                 );
             }
             broadcaster.notifyAll(miniModel);
         }).start());
 
         new Thread(() -> {
             try { launcher.run(); }
             catch (Exception e) {
                 System.out.println("Unexpected exception in game loop");
                 e.printStackTrace();
             }
         }).start();
 
         rmiServer.start();
         new Thread(() -> tcpServer.start()).start();
     }
 
 
     /**
      * Runs the event-processing loop until the thread is interrupted.
      *
      * <p>A {@link ConcurrentModificationException} is caught and logged rather
      */
     public void run() {
         while (true) {
             try {
                 eventProcessor.doFirstEvent();
             } catch (InterruptedException e) {
                 Thread.currentThread().interrupt();
                 break;
             }
         }
     }
 
 
     /**
      * Attempts to restore a previously saved game.
      *
      * <p>The save is discarded if all but at most one player was offline at the
      * time of the crash, since there would be nobody to resume the game with.
      * Otherwise, the model is restored, the player-list limit is set, and each
      * player who was offline at crash time is pre-populated as offline so the
      * reconnection flow can handle them correctly.
      *
      * @param gameController the controller that will receive the restored model.
      * @param playerList     the player-status map to populate.
      * @param saveManager    the save manager to load from.
      */
     private static void restoreGameIfSaved(
             GameController gameController,
             LimitedMap<String, Boolean> playerList,
             SaveManager saveManager) {
 
         Game game = saveManager.load();
         if (game == null) return;
 
         long disconnectedCount = game.getDisconnectedPlayers().entrySet().stream()
                 .filter(Map.Entry::getValue).count();
 
         if (disconnectedCount >= game.getNPlayers() - 1) {
             saveManager.delete();
             System.out.println("Save discarded: too many players were offline at crash time.");
             return;
         }
 
         gameController.setModel(game);
         playerList.setLimit(game.getNPlayers());
 
         for (Map.Entry<Player, Boolean> entry : game.getDisconnectedPlayers().entrySet()) {
             if (entry.getValue()) {
                 playerList.put(entry.getKey().getUserName(), false);
             }
         }
         System.out.println("Game restored from save (" + game.getNPlayers() + " players).");
     }
 
 
     /**
      * Lists active non-loopback IPv4 network interfaces and prompts the operator
      * to choose one. If only one interface is available it is selected automatically.
      *
      * @param scanner the scanner used to read the operator's choice.
      * @return the IPv4 address of the selected interface.
      * @throws Exception if no valid network interface is available.
      */
     public static String chooseNetworkInterface(Scanner scanner) throws Exception {
         List<String> ips = new ArrayList<>();
 
         Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
         while (interfaces.hasMoreElements()) {
             NetworkInterface ni = interfaces.nextElement();
             if (!ni.isUp() || ni.isLoopback() || ni.isVirtual()) continue;
 
             Enumeration<InetAddress> addresses = ni.getInetAddresses();
             while (addresses.hasMoreElements()) {
                 InetAddress addr = addresses.nextElement();
                 if (addr instanceof Inet4Address) {
                     System.out.println("[" + ips.size() + "] " + ni.getDisplayName()
                             + " -> " + addr.getHostAddress());
                     ips.add(addr.getHostAddress());
                 }
             }
         }
 
         if (ips.isEmpty()) throw new Exception("No active network interface available");
 
         if (ips.size() == 1) {
             System.out.println("One interface found, using: " + ips.get(0));
             return ips.get(0);
         }
 
         System.out.print("Choose interface: ");
         int choice = Integer.parseInt(scanner.nextLine().trim());
         return ips.get(choice);
     }
 }