package tmcsim.client;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;
import java.util.Vector;
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.UIManager;
import tmcsim.client.cadclientgui.CADClientGUI;
import tmcsim.common.SimulationException;
import tmcsim.interfaces.CADClientInterface;
import tmcsim.interfaces.CoordinatorInterface;
/**
* CADClient is the main class for the CAD Client application. The main method
* instantiates an instance of the CADClient object with the default properties
* file "..\config\CADClient.properties" or the first argument fom the command
* line invocation. Properties data values are used to bind socket communication
* between the CAD Client and the CAD Simulator. The CADClientModel object is
* instantiated and the CAD Client registers itself with the CAD Simulator.
* Finally, the CADClockView is initialized, the model-view and observer
* relationships are established, and the view is shown.
*
* The properties file contains the following data:
*
* -----------------------------------------------------------------------------
* Host Name The host name where the CAD Simulator is located.
* Port Number The port number that the CAD Simulator is bound on.
* CAD Position The integer (>= 0) position for this CAD Client.
* CAD User ID The unique user id for this CAD Client.
* Error File Filename of error logging file.
* -----------------------------------------------------------------------------
* Example File:
* CADSimulatorHost = localhost
* CADSimulatorSocketPort = 4444
* CADPosition = 1
* CADUserID = A12345
* ErrorFile = cad_client_err.txt
*
*
* @author Matthew Cechini (mcechini@calpoly.edu)
* @version $Date: 2009/04/17 16:27:47 $ $Revision: 1.8 $
*/
public class CADClockDisplay extends UnicastRemoteObject implements
CADClientInterface
{
/**
* Error logger.
*/
private static Logger cadClientLogger = Logger.getLogger("tmcsim.client");
/**
* 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 CADClientModel.
*/
private CADClientModel theClientScreenModel;
/**
* Instance of the CADClockView.
*/
private CADClockView theClientScreenView;
/**
* Instance of the CADCLientGUI Replaces CADClockView
*/
private CADClientGUI theClientGUI;
/**
* 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 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);
}
/**
* Constructor. Initialize data from parsed properties file. Create a socket
* connection to the CADSimulator. The ClientScreenModel is initialized with
* the input and output I/O streams for socket communication. The
* ClientScreenModel registers with the CAD Simulator, using CAD position
* and userID read in from the properties file. The ClientScreenView is then
* created and initialized and set as an observer of the model.
*
* A thread is created with the runnable ClientScreenModel and is started.
* When this thread is no longer alive, or the ClientScrenView and
* CADClientSocket are closed. The program then exits.
*
* @param propertiesFile File path (absolute or relative) to the properties
* file containing configuration data.
*/
public CADClockDisplay(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());
// Instantiate the CADScreenView and set up the model-view observer
// relationship.
theClientScreenView = new CADClockView();
theClientScreenView.setVisible(true);
try
{
while (true)
{
long simtime = theCoorInt.getCurrentSimulationTime();
//System.out.println("" + formatInterval(simtime));
theClientScreenView.updateTime("" + formatInterval(simtime));
Thread.sleep(1000);
}
} catch (InterruptedException ex)
{
Logger.getLogger(CADClockDisplay.class.getName()).log(Level.SEVERE, null, ex);
}
// Create the CAD Client thread to run the CADClientModel Object.
// Thread clientThread = new Thread(theClientScreenModel);
// clientThread.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);
}
try
{
// If the properties file does not specify a CAD position, prompt
// the
// user to select one. If the user selects a position, write the
// new properties values to the file. If the user cancels, else
// throw an exception.
if (cadClientProp.getProperty(PROPERTIES.CLIENT_CAD_POS.name) == null)
{
if (getCADPosition())
{
cadClientProp.store(new FileOutputStream(propertiesFile),
"");
} else
{
throw new SimulationException(
SimulationException.INITIALIZE_ERROR);
}
}
// If the properties file does not specifiy a CAD User ID, prompt
// the
// user to enter a value. If the user enters a valid ID, write the
// new properties values to the file. If the user cancels, else
// throw an exception.
if (cadClientProp.getProperty(PROPERTIES.CLIENT_USER_ID.name) == null)
{
if (getUserID())
{
cadClientProp.store(new FileOutputStream(propertiesFile),
"");
} else
{
throw new SimulationException(
SimulationException.INITIALIZE_ERROR);
}
}
} catch (IOException ioe)
{
cadClientLogger.logp(Level.SEVERE, "SimulationManager",
"Constructor",
"Exception in writing to the properties file.");
throw new SimulationException(SimulationException.INITIALIZE_ERROR);
}
// Ensure that the properties file has a valid display type
if (cadClientProp.getProperty(PROPERTIES.DISPLAY_TYPE.name) == null
|| (!cadClientProp.getProperty(PROPERTIES.DISPLAY_TYPE.name)
.equals("FULL_SCREEN") && !cadClientProp.getProperty(
PROPERTIES.DISPLAY_TYPE.name).equals("FRAME")))
{
cadClientLogger.logp(Level.SEVERE, "SimulationManager",
"Constructor", "Invalid display type.");
throw new SimulationException(SimulationException.INITIALIZE_ERROR);
}
return true;
}
/**
* This method prompts the user to select a value for the CAD position. If
* the user cancels the method returns false, else the Properties object is
* updated and true is returned.
*
* @return True if the user successfully selected a CAD position, false if
* not.
*/
private boolean getCADPosition()
{
Vector positions = new Vector();
for (int i = 0; i < 10; i++)
{
positions.add(i);
}
Object cadPos = null;
while (true)
{
cadPos = JOptionPane.showInputDialog(null,
"Please assign this workstation a CAD position number.",
"CAD Position Asignment", JOptionPane.QUESTION_MESSAGE,
null, positions.toArray(), positions.get(0));
// If the user pressed cancel, confirm the exit and return false.
if (cadPos == null)
{
if (JOptionPane
.showConfirmDialog(
null,
"CAD Client cannot load until a valid CAD "
+ "position has been selected. Do you wish to "
+ "cancel loading the CAD Client?",
"Confirm Exit", JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION)
{
return false;
}
} // Else the user selected a CAD position, exit the loop.
else
{
break;
}
}
cadClientProp.setProperty(PROPERTIES.CLIENT_CAD_POS.name,
cadPos.toString());
return true;
}
/**
* This method prompts the user to enter a 5-character User ID. If the user
* cancels the method returns false, else the Properties object is updated
* and true is returned.
*
* @return True if the user successfully selected a CAD position, false if
* not.
*/
private boolean getUserID()
{
String cadUID = null;
while (true)
{
cadUID = JOptionPane.showInputDialog(null,
"Please assign this workstation a 6-character CAD "
+ "User ID.", "CAD User ID Asignment",
JOptionPane.QUESTION_MESSAGE);
// /If the user pressed cancel, confirm the exit and return false.
if (cadUID == null)
{
if (JOptionPane.showConfirmDialog(null,
"CAD Client cannot load until a valid User ID "
+ "has been entered. Do you wish to "
+ "cancel loading the CAD Client?",
"Confirm Exit", JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION)
{
return false;
}
} // If the user does not enter a valid User ID, notify and reprompt.
else if (cadUID.length() != 6)
{
JOptionPane.showMessageDialog(null,
"The User ID must be 6 characters.", "Invalid User ID",
JOptionPane.WARNING_MESSAGE);
} // Else the user entered a valid value, exit the loop.
else
{
break;
}
}
cadClientProp.setProperty(PROPERTIES.CLIENT_USER_ID.name, cadUID);
return true;
}
/**
* 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 CADClockDisplay(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);
}
}
public void refresh()
{
theClientGUI.screen.refreshScreens();
}
public void ensureProperShutdown()
{
Runtime.getRuntime().addShutdownHook(new Thread()
{
public void run()
{
try
{
theCoorInt.unregisterForCallback(client);
} catch (RemoteException e)
{
e.printStackTrace();
}
}
});
}
}