package tmcsim.paramicscommunicator;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.Observable;
import java.util.Observer;
import java.util.Properties;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.UIManager;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import tmcsim.common.CADProtocol.PARAMICS_ACTIONS;
import tmcsim.common.CADProtocol.PARAMICS_COMM_TAGS;
import tmcsim.paramicscommunicator.FileIOUpdate.IO_TYPE;
import tmcsim.paramicscommunicator.FileRegUpdate.REG_TYPE;
import tmcsim.paramicscommunicator.gui.ParamicsCommunicatorGUI;
/**
* ParamicsCommunicator is the main class for this module. The Paramics
* Communicator is used to provide communication between the CAD Simulator and
* the Paramics traffic modeler. While the application is running, data is
* received on a socket from the CAD Simulator. Transmitted data are XML
* documents containing information and action commands. The CAD Simulator
* registers readers and writers with the ParamicsCommunicator. Any data read by
* a ParamicsReader is sent back to the CAD Simulator. All data to be written by
* a ParamicsWriter is received through the socket.
* The properties file for the ParamicsCommunicator class contains the following
* data.
*
* -----------------------------------------------------------------------------
* Socket Port The port number to use for socket communication.
* Working Directory The working directory use for Paramics file
* communication.
* Error File The target file to use for error logging.
* -----------------------------------------------------------------------------
* Example File:
* SocketPort = 4450
* WorkingDirectory = c:\\tmc_simulator\\
* ErrorFile = sim_mgr_error.xml
* -----------------------------------------------------------------------------
*
*
* @author Matthew Cechini (mcechini@calpoly.edu)
* @version $Date: 2009/04/17 16:27:46 $ $Revision: 1.7
*/
public class ParamicsCommunicator extends Observable implements Observer, Runnable
{
private static final String CONFIG_FILE_NAME = "paramics_communicator_config.properties";
/**
* Error logger.
*/
private static Logger paramLogger = Logger.getLogger("tmcsim.paramicscommunicator");
/**
* Enumeration containing property names.
*
* @author Matthew Cechini
*/
private static enum PROPERTIES
{
SOCKET_PORT("SocketPort"),
WORKING_DIR("WorkingDirectory"),
GUI_VISIBLE("GUIvisible");
public String name;
private PROPERTIES(String n)
{
name = n;
}
}
/**
* Properties object.
*/
private Properties paramicsCommProp = null;
/**
* Current working directory where files will be read and written
*/
private String workingDirectory = null;
/**
* Socket used to create socket communication with the CAD Simulator.
*/
private ServerSocket serverSocket = null;
/**
* Soccket used to communicate with CAD Simulator.
*/
private Socket paramicsSocket = null;
/**
* Input Stream for reading data from the CAD Simulator.
*/
private ObjectInputStream in = null;
/**
* Output Stream for writing data to the CAD Simulator.
*/
private ObjectOutputStream out = null;
/**
* Map of all current ParamicsFileWriters referenced by I/O ID.
*/
private TreeMap writers = null;
/**
* Map of all current ParamicsFileReaders referenced by I/O ID.
*/
private TreeMap readers = null;
/**
* The view class for the ParamicsCommunicator.
*/
private ParamicsCommunicatorGUI theGUI;
/**
* Constructor. Read in the property values. If the properties file does not
* contain a value for the working directory, open a dialog to prompt the
* user for the path of the Paramics working directory. An empty string is
* not accepted. A null signifies that the user pressed cancel. Prompt the
* user to accept the cancel and exit the application if confirmed. Continue
* until a valid directory has been entered, that exists, and append a '\'
* to the end of the directory if necessary.
*
* Initialize the Sockets and begin communication.
*
* @param propertiesFilePath File Path of ParamicsCommunicator properties
* file.
*/
public ParamicsCommunicator(String propertiesFile)
{
paramLogger.logp(Level.INFO, "ParamicsCommunicator", "Constructor",
"Entering ");
writers = new TreeMap();
readers = new TreeMap();
theGUI = new ParamicsCommunicatorGUI();
addObserver(theGUI);
theGUI.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
try
{
paramicsCommProp = new Properties();
paramicsCommProp.load(new FileInputStream(propertiesFile));
if (paramicsCommProp.getProperty(PROPERTIES.SOCKET_PORT.name) == null)
{
JOptionPane.showMessageDialog(theGUI,
"Properties file missing CAD Simulator Port information.",
"Invalid Configuration", JOptionPane.ERROR_MESSAGE);
System.exit(0);
} else if (paramicsCommProp.getProperty(PROPERTIES.WORKING_DIR.name) == null
|| paramicsCommProp.getProperty(PROPERTIES.WORKING_DIR.name).length() == 0)
{
try
{
String workingDir = null;
while (workingDir == null || workingDir.length() == 0)
{
workingDir = JOptionPane.showInputDialog(null,
"Please set the output directory for Paramics communication.",
"Paramics Working Directory", JOptionPane.QUESTION_MESSAGE);
if (workingDir == null)
{
} else if (!new File(workingDir).exists())
{
JOptionPane.showMessageDialog(null,
"Directory does not exist.",
"Invalid Working Directory", JOptionPane.WARNING_MESSAGE);
workingDir = null;
} else if (!new File(workingDir).isDirectory())
{
JOptionPane.showMessageDialog(null,
workingDir + " is not a directory.",
"Invalid Working Directory", JOptionPane.WARNING_MESSAGE);
workingDir = null;
}
}
if (workingDir.lastIndexOf("\\") != workingDir.length() - 1)
{
workingDir = workingDir + "\\";
}
paramicsCommProp.setProperty(PROPERTIES.WORKING_DIR.name, workingDir);
paramicsCommProp.store(new FileOutputStream(propertiesFile), "");
} catch (IOException ioe)
{
paramLogger.logp(Level.SEVERE, "ParamicsCommunicator", "Constructor",
"Exception in writing properties file.", ioe);
}
}
workingDirectory = paramicsCommProp.getProperty(
PROPERTIES.WORKING_DIR.name).trim();
} catch (Exception e)
{
paramLogger.logp(Level.SEVERE, "ParamicsCommunicator", "Constructor",
"Exception in reading properties file.", e);
}
// Should we display the GUI?
String visibleProp = paramicsCommProp.getProperty(PROPERTIES.GUI_VISIBLE.name);
// If no property was given, or if it was given and says True
if (visibleProp == null || (visibleProp.toLowerCase().equals("true")))
{
theGUI = new ParamicsCommunicatorGUI(); // it sets itself visible
addObserver(theGUI);
theGUI.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);
}
try
{
initializeSockets(Integer.parseInt(paramicsCommProp.getProperty(
PROPERTIES.SOCKET_PORT.name).trim()));
} catch (Exception e)
{
paramLogger.logp(Level.SEVERE, "ParamicsCommunicator", "Constructor",
"Exception in initializing sockets.", e);
}
}
/**
* Transmits a message XML document object to the CAD Simulator.
*
* @param mess The ParamicsCommMessage to be transmitted.
*/
private void write(Document mess)
{
synchronized (paramicsSocket)
{
try
{
out.writeObject(mess);
out.flush();
} catch (Exception e)
{
paramLogger.logp(Level.SEVERE, "ParamicsCommunicator", "write",
"Exception in writing to the socket.", e);
}
}
}
/**
* Observer/Observable update method. The Paramics Communicator observers
* registered ParamicsReaders. When messages are to be sent, they are sent
* through this method. All messages are ParamicsCommMessage objects. Send
* these messages to the write() method for transmission on the socket.
*/
public void update(Observable o, Object arg)
{
if (arg instanceof Document)
{
write((Document) arg);
}
}
/**
* Runnable method. While this thread is not interrupted, read in an object
* from the socket input stream. If an object exists, call doMessage() to
* parse and perform the received action in the message.
*/
public void run()
{
while (true)
{
try
{
doMessage((Document) in.readObject());
} catch (SocketTimeoutException ste)
{
//just try again
} catch (EOFException eofe)
{
paramLogger.logp(Level.SEVERE, "ParamicsCommunicator",
"run", "EOF Exception in reading data from the socket.", eofe);
} catch (Exception e)
{
paramLogger.logp(Level.SEVERE, "ParamicsCommunicator",
"run", "Exception in reading data from the socket.", e);
JOptionPane.showMessageDialog(theGUI,
"Connection has been lost to the CAD Simulator. "
+ "Paramics Communicator will now shutdown.",
"Dropped Connection", JOptionPane.ERROR_MESSAGE);
break;
}
try
{
Thread.sleep(2000);
paramLogger.logp(Level.INFO, "ParamicsCommunicator",
"run", "sleeping.");
} catch (InterruptedException ex)
{
paramLogger.logp(Level.INFO, "ParamicsCommunicator",
"run", "Exception in reading data from the socket.", ex);
}
}
try
{
in.close();
} catch (Exception e)
{
}
try
{
out.close();
} catch (Exception e)
{
}
try
{
serverSocket.close();
} catch (Exception e)
{
}
try
{
paramicsSocket.close();
} catch (Exception e)
{
}
}
/**
* Perform the action represented in XML document message received from the
* CAD Simulator. First determine if the action is from a READER, WRITER,
* and RESET. If the paramics action is REGISTER, add a new
* ParamicsFileReader/Writer to the local list of readers/writers and update
* the GUI with a FileRegUpdate object. If the paramics action is
* UNREGISTER, remove the ParamicsFileReader/Writer from the local list of
* readers/writers and update the GUI with a FileRegUpdate object. If RESET
* is received, clear all readers and writers.
*
* @param mess Received XML document message.
*/
private void doMessage(Document mess)
{
Element rootElement = mess.getDocumentElement();
String id = null;
String action = null;
switch (PARAMICS_COMM_TAGS.fromString(rootElement.getNodeName()))
{
case READER:
id = rootElement.getAttribute(PARAMICS_COMM_TAGS.ID.tag);
action = rootElement.getAttribute(PARAMICS_COMM_TAGS.ACTION.tag);
switch (PARAMICS_ACTIONS.fromString(action))
{
case REGISTER:
Integer interval = Integer.parseInt(rootElement.getChildNodes().item(0).getTextContent());
String targetFile = rootElement.getChildNodes().item(1).getTextContent();
readers.put(id, new ParamicsFileReader(workingDirectory, id,
interval, targetFile));
readers.get(id).addObserver(this);
readers.get(id).addObserver(theGUI);
setChanged();
notifyObservers(new FileRegUpdate(IO_TYPE.READ,
REG_TYPE.REGISTER, id, targetFile, interval));
break;
case UNREGISTER:
readers.get(id).deleteObserver(this);
readers.get(id).deleteObserver(theGUI);
readers.remove(id);
setChanged();
notifyObservers(new FileRegUpdate(IO_TYPE.READ,
REG_TYPE.UNREGISTER, id, null, null));
break;
}
break;
case WRITER:
id = rootElement.getAttribute(PARAMICS_COMM_TAGS.ID.tag);
action = rootElement.getAttribute(PARAMICS_COMM_TAGS.ACTION.tag);
switch (PARAMICS_ACTIONS.fromString(action))
{
case REGISTER:
String targetFile = rootElement.getChildNodes().item(0).getTextContent();
writers.put(id, new ParamicsFileWriter(id,
workingDirectory, targetFile));
writers.get(id).addObserver(theGUI);
setChanged();
notifyObservers(new FileRegUpdate(IO_TYPE.WRITE,
REG_TYPE.REGISTER, id, targetFile, null));
break;
case UNREGISTER:
writers.remove(id);
writers.get(id).deleteObserver(theGUI);
setChanged();
notifyObservers(new FileRegUpdate(IO_TYPE.WRITE,
REG_TYPE.UNREGISTER, id, null, null));
break;
case WRITE_FILE:
writers.get(id).writeMessage((Element) rootElement.getChildNodes().item(0));
break;
}
break;
case RESET:
readers.clear();
writers.clear();
break;
}
}
/**
* Method waits to accept a socket connection from the CAD Simulator. When a
* connection has been established the method exits. The input and output
* streams are created on the new socket.
*
* @param socketPort Socket port to use for establishing Socket
* communication.
* @throws IOException if there is an exception in establishing Socket
* communication.
*/
private void initializeSockets(Integer socketPort) throws IOException
{
boolean waiting = true;
try
{
serverSocket = new ServerSocket(socketPort);
//delay for accept timeout(milliseconds)
serverSocket.setSoTimeout(10 * 1000);
} catch (IOException ioe)
{
throw new IOException("Exception in creating "
+ "the server socket on port " + socketPort);
}
while (waiting)
{
try
{
paramicsSocket = serverSocket.accept();
waiting = false;
} catch (SocketTimeoutException ste)
{
System.out.println("...waiting...");
try
{
Thread.sleep(2000);
paramLogger.logp(Level.INFO, "ParamicsCommunicator",
"initializeSockets", "sleeping.");
} catch (InterruptedException ex)
{
paramLogger.logp(Level.INFO, "ParamicsCommunicator",
"initializeSockets", "Exception exiting for socket.", ex);
}
} catch (IOException ioe)
{
throw new IOException("Exception in creating "
+ "the receiving socket on port " + socketPort);
}
}
//** out must be performed before in to unlock for connecting socket **//
try
{
out = new ObjectOutputStream(paramicsSocket.getOutputStream());
in = new ObjectInputStream(paramicsSocket.getInputStream());
} catch (IOException ioe)
{
throw new IOException("Exception in creating input "
+ "and output streams on socket.");
}
}
/**
* Construct the ParamicsCommunicator 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 Thread(new ParamicsCommunicator(System.getProperty("CONFIG_DIR") + System.getProperty("file.separator") + CONFIG_FILE_NAME)).start();
} catch (Exception e) {
paramLogger.logp(Level.SEVERE, "ParamicsCommunicator", "Main",
"Error occured initializing application", e);
JOptionPane.showMessageDialog(null, e.getMessage(),
"Error - Program Exiting", JOptionPane.ERROR_MESSAGE);
System.exit(-1);
}
}
}