Coverage Summary for Class: RMIClient (it.polimi.ingsw.gc14.Network.RMI.Client)
| Class |
Class, %
|
Method, %
|
Branch, %
|
Line, %
|
| RMIClient |
0%
(0/1)
|
0%
(0/15)
|
0%
(0/12)
|
0%
(0/60)
|
package it.polimi.ingsw.gc14.Network.RMI.Client;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.concurrent.*;
import it.polimi.ingsw.gc14.Controller.ClientController;
import it.polimi.ingsw.gc14.ErrorType;
import it.polimi.ingsw.gc14.Network.IClient;
import it.polimi.ingsw.gc14.Network.NetworkConfig;
import it.polimi.ingsw.gc14.Network.RMI.Common.IGameServer;
/**
* Client RMI. Uses the methods exposed by the server RMI.
*/
public class RMIClient implements IClient {
/** Hostname or IP address of the RMI server. */
private final String host;
/** Port of the RMI registry. */
private final int port;
/** Remote stub obtained from the RMI registry after a successful connection. */
private IGameServer stub;
/** Client-side controller that receives and applies incoming game events. */
private final ClientController controller;
/** Local IP address advertised to the RMI runtime so callbacks are routed correctly. */
private final String myIp;
/** Username of the currently connected player; set during {@link #connect}. */
private String username;
/** {@code true} while the heartbeat loop is active; set to {@code false} on disconnect. */
private volatile boolean running = false;
/** Scheduler that fires ping() every {@value NetworkConfig#KEEPALIVE_INTERVAL_MS} ms. */
private ScheduledExecutorService pingSender;
/**
* Future executor used for timer Running
*/
private ExecutorService executor;
/**
* Constructs an RMI client and initializes its connection parameters.
*
* @param controller the client controller associated with this RMI client.
* @param host the hostname or IP address of the RMI server.
* @param port the port used to connect to the RMI registry.
* @param myIp the IP address of the client.
*/
public RMIClient(ClientController controller, String host, int port, String myIp) {
this.controller = controller;
this.host = host;
this.port = port;
this.myIp = myIp;
}
/**
* Connects to the RMI server and starts the heartbeat loop.
*
* <p>Mirrors {@code TCPClient.connect()}: after a successful join the
* heartbeat channel is opened (here: a scheduler is started instead of
* opening a second socket).
*/
@Override
public ErrorType connect(String username, int proposedNPlayers) {
try {
System.setProperty("java.rmi.server.hostname", this.myIp);
Registry registry = LocateRegistry.getRegistry(host, port);
this.stub = (IGameServer) registry.lookup("RMIGameServer");
this.username = username;
ClientCallbackImpl callback = new ClientCallbackImpl(controller);
ErrorType status = stub.joinGame(username, proposedNPlayers, callback);
if(status==null)
{
running = true;
startHeartbeat();
return null;
}
return status;
} catch (Exception e) {
e.printStackTrace();
return ErrorType.SERVER_UNREACHABLE;
}
}
// -------------------------------------------------------------------------
// Heartbeat — mirrors TCPClient.heartbeatLoop()
// -------------------------------------------------------------------------
/**
* Starts sending periodic pings to the server.
*
* <p>Mirrors the {@code ScheduledExecutorService} in
* {@code TCPClient.heartbeatLoop()} that writes {@code PING} every 3 s.
* On {@link RemoteException} the server is considered gone and
* {@link #disconnect()} is called — mirrors the behavior on
* {@code SocketTimeoutException} / {@code IOException} in the TCP version.
*/
private void startHeartbeat() {
pingSender = Executors.newSingleThreadScheduledExecutor(r -> {
Thread t = new Thread(r, "rmi-heartbeat");
t.setDaemon(true);
return t;
});
executor = Executors.newSingleThreadExecutor();
pingSender.scheduleAtFixedRate(() -> {
Future<?> future = executor.submit(() -> {
try {
stub.ping(username);
} catch (RemoteException e) {
disconnect();
controller.getView().showError(ErrorType.SERVER_CRASHED,ErrorType.SERVER_CRASHED.toString());
}
});
try {
future.get(NetworkConfig.SILENCE_THRESHOLD_MS, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
future.cancel(true);
controller.getView().showError(ErrorType.SERVER_CRASHED,ErrorType.SERVER_CRASHED.toString());
disconnect();
} catch (Exception e) {
disconnect();
controller.getView().showError(ErrorType.SERVER_CRASHED,ErrorType.SERVER_CRASHED.toString());
}
}, 0, NetworkConfig.KEEPALIVE_INTERVAL_MS, TimeUnit.MILLISECONDS);
}
/**
* Tears down the connection.
* Mirrors {@code TCPClient.disconnect()}: stops the heartbeat and notifies
* the view.
*/
private void disconnect() {
if (!running) return;
running = false;
if (pingSender != null) pingSender.shutdownNow();
if (executor != null) executor.shutdownNow();
controller.setModel(null);
controller.setClient(null);
}
/**
* 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) {
try{
stub.drawUpperTribeCard(playerUsername,pos);
}
catch (RemoteException e){
System.out.println("Error during remote draw upper tribe card");
}
}
/**
* 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) {
try{
stub.drawLowerTribeCard(playerUsername,pos);
}
catch (RemoteException e){
System.out.println("Error during remote draw lower tribe card");
}
}
/**
* 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) {
try{
stub.drawUpperBuildingCard(playerUsername,pos);
}
catch (RemoteException e){
System.out.println("Error during remote draw upper building card");
}
}
/**
* 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) {
try{
stub.drawLowerBuildingCard(playerUsername,pos);
}
catch (RemoteException e){
System.out.println("Error during remote draw lower building card");
}
}
/**
* 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) {
try{
stub.skipTurn(playerUsername);
}
catch (RemoteException e){
System.out.println("Error during remote skip turn");
}
}
/**
* 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) {
try{
stub.slotChoice(playerUsername,pos);
}
catch (RemoteException e){
System.out.println("Error during remote slot choice");
}
}
/**
* Sends a totem choice to the server for the specified player via RMI.
*
* @param playerUsername the username of the player choosing the totem.
* @param totem the name of the chosen totem.
*/
public void totemChoice(String playerUsername,String totem) {
try{
stub.totemChoice(playerUsername,totem);
}
catch (RemoteException e){
System.out.println("Error during remote totem choice");
}
}
/**
* Notifies the server of a voluntary disconnection via RMI, then stops
* the keep-alive scheduler.
*/
public void notifyDisconnection() {
running = false;
if (pingSender != null) pingSender.shutdownNow();
if (executor != null) executor.shutdownNow();
try {
stub.disconnectPlayer(username);
} catch (RemoteException e) {
System.out.println("Error during remote disconnection");
}
}
}