/* ========================
* JSynoptic : a free Synoptic editor
* ========================
*
* Project Info: http://jsynoptic.sourceforge.net/index.html
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation;
* either version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* (C) Copyright 2001-2003, by :
* Corporate:
* Astrium SAS
* EADS CRC
* Individual:
* Nicolas Brodu
* Christian Fleischer
*
* $Id: SocketDataSourceCollection.java,v 1.3 2008/02/25 11:33:45 ogor Exp $
*
* Changes
* -------
* 02-Dec-2003 : Creation Date (CF);
*
*/
package examples.socket.plugin;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
//import javax.swing.Timer;
import javax.swing.JOptionPane;
import javax.swing.undo.CompoundEdit;
import java.lang.NumberFormatException;
import jsynoptic.base.ContextualActionProvider;
import jsynoptic.ui.LongAction;
import simtools.data.DataInfo;
import simtools.data.DynamicDataSourceCollection;
import simtools.data.UnsupportedOperation;
import simtools.data.ValueProvider;
import simtools.data.buffer.DelayedBuffer;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
/****************************************************************************
* This class provides the low-level access to sockets that
* is used in the SocketDataSourceCollection later
****************************************************************************/
class DynamicDataSocket {
protected ServerSocket server = null;
protected Socket client = null;
protected BufferedReader in = null;
/*****************************************************************************
* closes all socket-communication objects
* and sets values to "null" to show that no connection exists
*****************************************************************************/
protected void close() {
try{
if (in != null) // close only if valid
in.close(); // close
in= null; // keep in mind that connection is closed
if (server != null) // close only if valid
server.close(); // close
server= null; // keep in mind that connection is closed
} catch (IOException e) {
System.out.println("Could not close.");
}
}
/*****************************************************************************
* connects the plugin to the port with the number "portNumber";
* throws some exceptions if that is not possible;
* the waiting is done with a timeout, and the
* SocketTimeoutException is thrown, if the timeout is met.
* sets also a buffered reader to get the data; the buffer reader
* has a very short timeout for the polling-mode
*****************************************************************************/
public void open(int portNumber) throws SocketTimeoutException,IOException {
try {
server= new ServerSocket(portNumber); // create a new socket object as a server
server.setSoTimeout(10000); // set the timeout for the accept-operation
client = server.accept(); // wait (with timeout) for a client to connect
client.setSoTimeout(1); // set the timeout for all read operations
in = new BufferedReader(new InputStreamReader(client.getInputStream())); // create a comfortable buffered reader for the socket
} catch (SocketTimeoutException E) {
close(); // close the server-object before...
throw E; // passing the exception on...
} catch (IOException E) {
close(); // close the server-object before...
throw E; // passing the exception on...
}
}
/*****************************************************************************
* reads a line (waits for CR!!) from the client;
* if a timeout in the bind(...) method is set it only waits that amount
* of time before returning "null" if no data is read;
* otherwise it just returns the string that has been read
*****************************************************************************/
public String readData() {
try {
return in.readLine(); // read a complete line (waits for CR!!!) from the client
} catch (SocketTimeoutException e) {
} catch (IOException e) {
System.out.println("Read failed!");
}
return null; // return empty line if we couldn't read anything
}
/****************************************************************************
* just the default-constructor
*****************************************************************************/
public DynamicDataSocket() {
// currently nothing needs to be done here...
}
/****************************************************************************
* we need to clean up some stuff here...
*****************************************************************************/
protected void finalize() {
close(); // release the socket connection (close)...
}
}
/****************************************************************************
* This class is the DynamicDataSourceCollection for data
* that is received over a socket
*****************************************************************************/
public class SocketDataSourceCollection extends DynamicDataSourceCollection implements ActionListener, ContextualActionProvider {
protected DynamicDataSocket socket= new DynamicDataSocket(); // the socket that is used
//protected Timer timer= new Timer(10, this); // the timer that calls the read-method periodically (polling)
protected Thread thread = new Thread() {
public void run() {
while (!isInterrupted()) actionPerformed(null);
}
};
/****************************************************************************
* Adds a new data source to the collection
****************************************************************************/
public void addSource() {
DataInfo di = new DataInfo( // Specify the data information
"Socket source "+size(), // Label of the data source : what is displayed
"socket"+size(), // Id of the data source : must be unique. Also used by the source providers (see the random plugin)
"data received over a socket connection\n (element number "+size()+" in a space separated line with CR)", // a comment, optional
"(unknown)" // this data source unit, optional
);
createDataSource(di, ValueProvider.DoubleProvider); // create the source, with data type double
try { // Wrap a buffer around it so we see past values too
bufferize(size() -1, new DelayedBuffer(ValueProvider.DoubleProvider, 1000));
} catch (UnsupportedOperation e) {
e.printStackTrace();
}
}
/****************************************************************************
* If a source is sorted, this can lead to some optimization.
****************************************************************************/
public int sortedOrder(int i) {
if (i==0)
return 1; // time is in ascending order
return super.sortedOrder(i);
}
/****************************************************************************
* Returns information about the collection itself
****************************************************************************/
public DataInfo getInformation() {
return new DataInfo(
"Socket Connection", // The name of this data source collection, as appears in the source pane
"SocketConnection" // A unique ID. This example supposes there is only one instance of this object
);
}
/****************************************************************************
* (Part of the ContextualActionProvide interface
* This adds a contextual popup menu when
* right-clicking on the collection in the source pane)
*
* Return the list of possible actions
****************************************************************************/
public String[] getActions(double x, double y, Object o, int context) {
if (context != SOURCELIST_CONTEXT)
return null;
return new String[] { "Add Source", "Bind to Socket", "Release Socket" };
}
/****************************************************************************
* Analyses the String passed as "Input" and extracts the values
* contained in it; the values are written into the data-buffers
* in the order they appear in the string
****************************************************************************/
protected void processInput(String _Input) {
if (_Input == null) // nothing valid is read...
return; // so return and don't put any data anywhere
// some stupid apps, like labview, produce fractional numbers like 3,123 and not 3.123 !!
// convert all "," to "."
String Input= "";
for (int i= 0; i < _Input.length(); i++)
Input+= _Input.charAt(i) == ',' ? '.' : _Input.charAt(i);
String[] StrValues= Input.split(" "); // split the String at every space to separate the values from each other
setDoubleValue(0, lastIndex+1); // write the packet number in the default buffer 0
int BuffNumber= 1; // number of the buffer where the data is put (first one after the default-buffer (packet index))...
for (int i= 0; i < StrValues.length && BuffNumber < size(); i++) { // loop over all values in the string (but only as long as there are enough buffers (without default-buffer: -1!))
try {
//System.out.println(BuffNumber+"<"+StrValues[i]+">"); // DEBUG only => resource consuming
setDoubleValue(BuffNumber, Double.parseDouble(StrValues[i])); // write the values from the string into the buffer
BuffNumber++;
} catch (NumberFormatException E) {
}
}
registerNewValues(); // put all values in the buffers so that they are displayed
}
/****************************************************************************
* (Timer-Interface)
* This method is called by the timer each time it triggers;
* reads data from the socket
****************************************************************************/
public void actionPerformed(ActionEvent e) {
processInput(socket.readData()); // read the data from the socket and process it
}
/****************************************************************************
* Do one of the actions previously declared by getAction.
*
* @param x Coordinate, for example mouse position
* @param y Coordinate, for example mouse position
* @param o Object the action should work on.
* @param action An action returned by a previous getActions call with the same x, y, o parameters
* It may be null, in which case the default action is requested for this x,y,o.
* @return true if the action could be performed
****************************************************************************/
public boolean doAction(double x, double y, Object o, String action, CompoundEdit undoableEdit) {
// bind is selected, so try it!
if (action.equals("Bind to Socket")) {
new LongAction(LongAction.LONG_ACTION_SOURCE,this) {
protected void doAction() {
String input= JOptionPane.showInputDialog(null, "Enter a port number to open (e.g. 4444)\n(Opening will be done with a timeout after clicking on OK):","4444");
if (input != null) {
int portNumber= Integer.parseInt(input);
try {
socket.open(portNumber); // try to open the socket connection (wait for client)
// timer.start(); // start timer for polling on the socket
thread.start();
} catch (SocketTimeoutException e) {
JOptionPane.showMessageDialog(null, "Couldn't bind to port "+String.valueOf(portNumber)+": \nTimeout.\nMaybe the client didn't connect in time?");
} catch (IOException e) {
JOptionPane.showMessageDialog(null, "Couldn't bind to port "+String.valueOf(portNumber)+". \nMaybe it is already in use (try \"Release Socket\") or blocked by a firewall.");
}
}
}
}.start(true);
}
// release is selected, so do it!
if (action.equals("Release Socket")) {
// timer.stop(); // no more polling on the socket
thread.interrupt();
socket.close(); // close the socket
}
// the user wants to add another source-channel, so add one
if (action.equals("Add Source"))
addSource(); // add a new channel
return true;
}
/****************************************************************************
* Returns true if, and only if, it is possible to do the action right now
* @param x Coordinate, for example mouse position
* @param y Coordinate, for example mouse position
* @param o Object the action should work on.
* @param action An action returned by a previous getActions call with the same x, y, o parameters
* It may be null, in which case the default action is requested for this x,y,o.
* @param context one of the context defined in the ContextualActionProvider class
* @return true if the action can be performed
****************************************************************************/
public boolean canDoAction(double x, double y, Object o, String action, int context) {
return true; // This example object is always ready to perform the actions it declared
}
/****************************************************************************
* This constructor sets one default-source and some data sources
* (index of received packets = number of packets).
****************************************************************************/
public SocketDataSourceCollection() {
// Create a first source called "packet index", useful to make XY plots.
// See addSource() for comments
createDataSource(new DataInfo("packet index","packet index","number of packets received",""), ValueProvider.DoubleProvider);
try { // Wrap a buffer around it so we see past values too
bufferize(0, new DelayedBuffer(ValueProvider.DoubleProvider, 1000));
} catch (UnsupportedOperation e) {
e.printStackTrace();
}
for (int i= 0; i < 10; i++)
addSource();
}
}