package tmcsim.client; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.FileInputStream; import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.util.Properties; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JOptionPane; import javax.swing.JWindow; import javax.swing.Timer; import javax.swing.UIManager; import tmcsim.common.SimulationException; import tmcsim.interfaces.CADClientInterface; import tmcsim.interfaces.CoordinatorInterface; /** * ClockClient shows the simulation clock time. It operates as a client of the * CAD server, using RMI to poll the server every second for the current * simulation clock time. * * @author jdalbey */ public class ClockClient extends UnicastRemoteObject implements CADClientInterface { /** * Error logger. */ private static Logger cadClientLogger = Logger.getLogger("tmcsim.client"); @Override public void refresh() throws RemoteException { // We have no responsibilities when Coordinator invokes this // Fixes #174 } /** * Enumeration containing properties name values. See CADClient class * description for more information. * * @author Matthew Cechini * @see CADClient */ private static enum PROPERTIES { CAD_SIM_HOST("CADSimulatorHost"), CAD_SIM_PORT("CADSimulatorSocketPort"), CAD_RMI_PORT( "CADRmiPort"), CLIENT_CAD_POS("CADPosition"), CLIENT_USER_ID( "CADUserID"), KEYBOARD_TYPE("KeyboardType"), DISPLAY_TYPE( "DisplayType"); public String name; private PROPERTIES(String n) { name = n; } } /** * CADClientSocket Object to handle socket communication between the Client * and CAD Simulator. */ private CADClientSocket theClientSocket; /** * Instance of the ClockView. */ private ClockView theView; /** * Properties object for the CADClient class. */ private Properties cadClientProp; /** * RMI interface for communication with the remote Coordinator. */ private static CoordinatorInterface theCoorInt; /** * reference to itself to be used for disconnecting from CADSimulator */ private CADClientInterface client = this; private static final String CONFIG_FILE_NAME = "cad_client_config.properties"; private final static int ONE_SECOND = 1000; /** * Constructor. Initialize data from parsed properties file. Create a socket * connection to the CADSimulator. * * @param propertiesFile File path (absolute or relative) to the properties * file containing configuration data. */ public ClockClient(String propertiesFile) throws SimulationException, RemoteException { if (!verifyProperties(propertiesFile)) { System.exit(0); } connect(cadClientProp.getProperty(PROPERTIES.CAD_SIM_HOST.name).trim(), cadClientProp.getProperty(PROPERTIES.CAD_RMI_PORT.name).trim()); theView = new ClockView(); theView.setVisible(true); // Create a timer that fetches the simulation time every second. Timer timer = new Timer(ONE_SECOND, new ActionListener() { public void actionPerformed(ActionEvent e) { try { long simtime = theCoorInt.getCurrentSimulationTime(); theView.updateTime("" + formatInterval(simtime)); } catch (RemoteException ex) { Logger.getLogger(ClockClient.class.getName()).log(Level.SEVERE, null, ex); } } }); timer.start(); ensureProperShutdown(); } /** * Connect to the Coordinator's RMI object, and register this object for * callback with the Coordinator. * * @param hostname Host name of the CAD Simulator. * @param portNumber Port number of the CAD Simulator RMI communication. * @throws SimulationException if there is an error creating the RMI * connection. */ protected void connect(String hostname, String portNumber) throws SimulationException { String coorIntURL = ""; try { coorIntURL = "rmi://" + hostname + ":" + portNumber + "/coordinator"; theCoorInt = (CoordinatorInterface) Naming.lookup(coorIntURL); theCoorInt.registerForCallback(this); } catch (Exception e) { throw new SimulationException(SimulationException.CAD_SIM_CONNECT, e); } } /** * This method verifies that the CAD Simulator Host and Port values are not * null. Also, if a CAD Position or User ID do not exist in the properties * file, the user is prompted to enter values. These values are written to * the properties file. If the user cancels the process of entering these * values, the verification fails. * * @param propertiesFile File path (absolute or relative) to the properties * file containing configuration data. * @return True if the properties file is valid, false if not. * @throws SimulationException if there is an exception in verifying the * properties file, or if the user cancels input. */ private boolean verifyProperties(String propertiesFile) throws SimulationException { // Load the properties file. try { cadClientProp = new Properties(); cadClientProp.load(new FileInputStream(propertiesFile)); } catch (Exception e) { cadClientLogger.logp(Level.SEVERE, "SimulationManager", "Constructor", "Exception in reading properties file.", e); throw new SimulationException(SimulationException.INITIALIZE_ERROR, e); } // Ensure that the properties file does not have null values for the // CAD Simulator's connection information. if (cadClientProp.getProperty(PROPERTIES.CAD_SIM_HOST.name) == null || cadClientProp.getProperty(PROPERTIES.CAD_SIM_PORT.name) == null) { cadClientLogger.logp(Level.SEVERE, "SimulationManager", "Constructor", "Null value in properties file."); throw new SimulationException(SimulationException.INITIALIZE_ERROR); } return true; } /** * Format a time in seconds as HH:MM:SS * * @param l * @return */ private String formatInterval(final long l) { final long hr = TimeUnit.SECONDS.toHours(l); final long min = TimeUnit.SECONDS.toMinutes(l - TimeUnit.HOURS.toSeconds(hr)); final long sec = TimeUnit.SECONDS.toSeconds(l - TimeUnit.HOURS.toSeconds(hr) - TimeUnit.MINUTES.toSeconds(min)); return String.format("%02d:%02d:%02d", hr, min, sec); } public void ensureProperShutdown() { Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { try { theCoorInt.unregisterForCallback(client); } catch (RemoteException e) { e.printStackTrace(); } } }); } /** * Construct the CADClient with the properties file path, either from the * command line arguments or default. * * @param args Command line arguments. */ public static void main(String[] args) { if (System.getProperty("CONFIG_DIR") == null) { System.setProperty("CONFIG_DIR", "config"); } try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); new ClockClient(System.getProperty("CONFIG_DIR") + System.getProperty("file.separator") + CONFIG_FILE_NAME); } catch (Exception e) { cadClientLogger.logp(Level.SEVERE, "SimulationManager", "Main", "Error initializing application."); JOptionPane.showMessageDialog(new JWindow(), e.getMessage(), "Error - Program Exiting", JOptionPane.ERROR_MESSAGE); System.exit(-1); } } }