package tmcsim.client; import atmsdriver.ATMSDriver; import atmsdriver.ConsoleDriver; import atmsdriver.ExchangeInfo; import atmsdriver.model.Highways; import atmsdriver.model.Station; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.InputMismatchException; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Queue; import java.util.Scanner; 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; /** * Skeleton for ATMS Driver that reads a "batch" file of highway * status update commands. * It operates as a client of the * CAD server, using RMI to poll the server every second for the current * simulation clock time. It uses the simulation clock time * to fire update commands at the desired time. * Note: Sim Mgr must be running before starting this application. * TODO: We probably want to be able to "override" a command, to force * clearing an incident. * @author jdalbey */ public class ATMSBatchDriver extends UnicastRemoteObject implements CADClientInterface { private static final String CONFIG_FILE_NAME = "cad_client_config.properties"; private final static int ONE_SECOND = 1000; private static final int FEPSIM_INTERVAL = 30000; private final static SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss"); /** * Error logger. */ private static Logger cadClientLogger = Logger.getLogger("tmcsim.client"); @Override public void refresh() throws RemoteException { System.out.println("ATMSBatchDriver.refresh() was invoked."); } /** * 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; /** * 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; /** * Highways in traffic network */ final private Highways highways; /** * Queue of batch events */ private Queue eventQueue; /** * Map of incidents to events */ private Map> incidents; /** Instance of ConsoleDriver that contains the highway model */ private ConsoleDriver console; /** GUI for this driver */ private ATMSBatchViewer theView; /** * 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 ATMSBatchDriver(String propertiesFile) throws SimulationException, RemoteException { if (!verifyProperties(propertiesFile)) { System.exit(0); } incidents = new HashMap> (); highways = new Highways( "config/vds_data/lds.txt", "config/vds_data/loop.txt", "config/vds_data/highwaysMeta.txt", "localhost", 8080); // Create console driver but don't start run() method console = new ConsoleDriver(highways); connect(cadClientProp.getProperty(PROPERTIES.CAD_SIM_HOST.name).trim(), cadClientProp.getProperty(PROPERTIES.CAD_RMI_PORT.name).trim()); // READ THE BATCH FILE OF COMMANDS and put in a queue readBatchFile(); // Launch the display theView = new ATMSBatchViewer(); theView.setVisible(true); theView.update("0:00", eventQueue); // Create a timer that fetches the simulation time every second. Timer timer = new Timer(ONE_SECOND, new ActionListener() { // Every second, see if an event should be launched public void actionPerformed(ActionEvent e) { String currentClock = ""; Date simClock = new Date(); // Obtain the simulation time from the CAD server try { long simtime = theCoorInt.getCurrentSimulationTime(); currentClock = formatInterval(simtime); try { simClock = formatter.parse(currentClock); } catch (ParseException ex) { Logger.getLogger(ATMSBatchDriver.class.getName()).log(Level.SEVERE, null, ex); System.out.println("Invalid simulation clock time found in ATMSDriverClient"); System.exit(-1); } //System.out.println("Current clock: " + currentClock); } catch (RemoteException ex) { Logger.getLogger(ATMSBatchDriver.class.getName()).log(Level.SEVERE, null, ex); } // If we have any events left to process if (!eventQueue.isEmpty()) { // Get the time to launch the next event String nextEvent = eventQueue.peek(); // String eventTimeField = nextEvent.substring(0,8); Scanner evtScan = new Scanner(nextEvent); String inci = evtScan.next(); String eventTimeField = evtScan.next(); Date eventTime = new Date(); try { eventTime = formatter.parse(eventTimeField); } catch (ParseException ex) { Logger.getLogger(ATMSBatchDriver.class.getName()).log(Level.WARNING, null, ex); System.out.println("Unable to parse event time: " + nextEvent + " skipping."); eventQueue.remove(); } //System.out.println("Next event will be launched at: " + formatter.format(eventTime)); theView.update(currentClock, eventQueue); // Check the queue of events to see if the first // item should be launched. IF so, // issue that command and remove it from queue. if (eventTime.before(simClock) || eventTime.equals(simClock)) { System.out.println("LAUNCHING EVENT at " + nextEvent ); // Extract fields from event and prepare them Scanner lineScan = new Scanner(nextEvent); try { lineScan.next(); // skip incident number field lineScan.next(); // skip time field int routeNumber = lineScan.nextInt(); Station.DIRECTION dir = Station.DIRECTION.toDirection(lineScan.next()); double postmile = lineScan.nextDouble(); int range = lineScan.nextInt(); ConsoleDriver.DOTCOLOR dotcolor = ConsoleDriver.DOTCOLOR.toDotColor(lineScan.next()); // apply colorization to highways console.applyColorToHighwayStretch(routeNumber, dir, postmile, range, dotcolor); // Remove this event from the queue, we're done with it. eventQueue.remove(); } catch (InputMismatchException ex) { System.out.println("Wrong format data in batch event file: " + nextEvent + " \nskipping."); eventQueue.remove(); } } } } }); timer.start(); // Start the FEP thread (to update ATMS every 30 sec). (See class def below) new WriteToFEPThread().run(); ensureProperShutdown(); } private void readBatchFile() { FileInputStream fis; try { fis = new FileInputStream("config/vds_data/atmsBatchEvents.txt"); eventQueue = new LinkedList(); // Read all lines from the file of events Scanner scan = new Scanner(fis); while (scan.hasNext()) { // Read a line and add it to the event queue String line = scan.nextLine(); eventQueue.add(line); // Parse the incident from the line Scanner lineScan = new Scanner(line); String incident = lineScan.next(); // Add the line to the list for the corresponding incident List evtList; if (incidents.containsKey(incident)) { evtList = incidents.get(incident); } else { evtList = new ArrayList(); } evtList.add(line); // and put it back in the map incidents.put(incident, evtList); } } catch (FileNotFoundException ex) { Logger.getLogger(ATMSBatchDriver.class.getName()).log(Level.SEVERE, null, ex); } System.out.println("Events file read, " + eventQueue.size() + " events queued."); } /** * 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 ATMSBatchDriver(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); } } class WriteToFEPThread extends Thread { public void run() { System.out.println("WriteToFEP Thread starting."); // Run indefinitely while (true) { try { // Write the highway network status to the FEP Simulator highways.writeToFEP(); } catch (SimulationException ex) { System.out.println("Skipping writeToFEP..."); } // Wait for FEP Sim to process the data we just sent try { Thread.sleep(FEPSIM_INTERVAL); } catch (InterruptedException ie) { ie.printStackTrace(); } } } } }