/**
* a software bus package
*
* @author Yannick Jestin
* @author http://www.tls.cena.fr/products/ivy/
*
*
*Ivy bus = new Ivy("Dummy agent","ready",null);
*bus.bindMsg("(.*)",myMessageListener);
*bus.start(null);
*
*
* CHANGELOG:
* 1.2.3:
* - adds a IVYBUS property to propagate the domain once set. This way,
* children forked through Ivy java can inherit from the current value.
* - adds synchronized flags to allow early disconnexion
* 1.2.2:
* added the String domains(String d) function, in order to display the
* domain list
* 1.2.1:
* bus.start(null) now starts on DEFAULT_DOMAIN
* added the getDomains in order to correctly display the domain list
* checks if the serverThread exists before interrupting it
* no has unBindMsg(String)
* 1.2.0:
* setSoTimeout is back on the server socket
* added a regression test main()
* clients is now a Hashtable. the deletion now works better
* getIvyClientsByName allows the research of IvyClient by name
* getDomain doesnt throw IvyException anymore
* removed the close() disconnect(IvyClient c). Fixes a big badaboum bug
* getDomain becomes public
* adding the sendToSelf feature
* fixed the printStackTrace upon closing of the ServerSocket after a close()
*/
package fr.dgac.ivy ;
import java.net.*;
import java.io.*;
import java.util.*;
public class Ivy implements Runnable {
/**
* the name of the application on the bus
*/
String appName;
/**
* the protocol version number
*/
public static final int PROCOCOLVERSION = 3 ;
/**
* the port for the UDP rendez vous, if none is supplied
*/
public static final int DEFAULT_PORT = 2010 ;
/**
* the domain for the UDP rendez vous
*/
public static final String DEFAULT_DOMAIN = "127.255.255.255:"+DEFAULT_PORT;
/**
* the library version, useful for development purposes only, when java is
* invoked with -DIVY_DEBUG
*/
public static final String libVersion ="1.2.3";
private boolean debug;
private static int serial=0; /* an unique ID for each regexp */
private static int clientSerial=0; /* an unique ID for each IvyClient */
private ServerSocket app;
private Vector watchers;
private volatile Thread serverThread; // to ensure quick communication of the end
private Hashtable callbacks = new Hashtable();
private Hashtable clients = new Hashtable();
private Vector ivyApplicationListenerList = new Vector();
private String messages_classes[] = null;
private boolean sendToSelf = false ;
private boolean stopped = false;
int applicationPort; /* Application port number */
Hashtable regexp_out = new Hashtable();
String ready_message = null;
public final static int TIMEOUTLENGTH = 3000;
/**
* Readies the structures for the software bus connexion.
*
* All the dirty work is done un the start() method
* @see #start
* @param name The name of your Ivy agent on the software bus
* @param message The hellow message you will send once ready
* @param appcb A callback handling the notification of connexions and
* disconnections, may be null
*/
public Ivy( String name, String message, IvyApplicationListener appcb) {
appName = name;
ready_message = message;
debug = (System.getProperty("IVY_DEBUG")!=null);
if ( appcb != null ) ivyApplicationListenerList.addElement( appcb );
}
/**
* connects the Ivy bus to a domain or list of domains.
*
* One thread (IvyWatcher) for each traffic rendezvous (either UDP broadcast or TCPMulticast)
* One thread (serverThread/Ivy) to accept incoming connexions on server socket
* a thread for each IvyClient when the connexion has been done
*
* @param domainbus a domain of the form 10.0.0:1234, it is similar to the
* netmask without the trailing .255. This will determine the meeting point
* of the different applications. Right now, this is done with an UDP
* broadcast. Beware of routing problems ! You can also use a comma
* separated list of domains.
*
*/
public void start(String domainbus) throws IvyException {
if (domainbus==null) domainbus=DEFAULT_DOMAIN;
Properties sysProp = System.getProperties();
sysProp.put("IVYBUS",domainbus);
try {
app = new ServerSocket(0);
app.setSoTimeout(TIMEOUTLENGTH);
applicationPort = app.getLocalPort();
} catch (IOException e) {
throw new IvyException("can't open TCP service socket " + e );
}
traceDebug("lib: "+libVersion+" protocol: "+PROCOCOLVERSION+" TCP service open on port "+applicationPort);
watchers = new Vector();
Domain[] d = parseDomains(domainbus);
// readies the rendezvous : an IvyWatcher (thread) per domain bus
for (int index=0;indexThere is one thread for each client connected, we could also
* create another thread each time we send a message.
* @param message A String which will be compared to the regular
* expressions of the different clients
* @return the number of messages actually sent
*/
public int sendMsg( String message ) {
int count = 0;
// an alternate implementation would one sender thread per client
// instead of one for all the clients. It might be a performance issue
for ( Enumeration e=clients.elements();e.hasMoreElements();) {
IvyClient client = (IvyClient)e.nextElement();
count += client.sendMsg( message );
}
if (sendToSelf) {
// TODO
}
return count;
}
/**
* Subscribes to a regular expression.
*
* The callback will be executed with
* the saved parameters of the regexp as arguments when a message will sent
* by another agent. A program doesn't receive its own messages.
* Example:
*
the Ivy agent A performs
b.bindMsg("^Hello (*)",cb);
*
the Ivy agent B performs b2.sendMsg("Hello world");
*
a thread in A will uun the callback cb with its second argument set
* to a array of String, with one single element, "world"
* @param regexp a perl regular expression, groups are done with parenthesis
* @param callback any objects implementing the IvyMessageListener
* interface, on the AWT/Swing framework
* @return the id of the regular expression
*/
public int bindMsg(String regexp, IvyMessageListener callback ) {
// creates a new binding (regexp,callback)
Integer key = new Integer(serial++);
regexp_out.put(key,regexp);
callbacks.put(key,callback );
// notifies the other clients this new regexp
for (Enumeration e=clients.elements();e.hasMoreElements();) {
IvyClient c = (IvyClient)e.nextElement();
c.sendRegexp(key.intValue(),regexp);
}
return key.intValue();
}
/**
* unsubscribes a regular expression
*
* @param id the id of the regular expression, returned when it was bound
*/
public void unBindMsg(int id) throws IvyException {
Integer key = new Integer(id);
if ( ( regexp_out.remove(key) == null )
|| (callbacks.remove(key) == null ) ) {
throw new IvyException("client wants to remove an unexistant regexp "+id);
}
for (Enumeration e=clients.elements();e.hasMoreElements();) {
((IvyClient)e.nextElement()).delRegexp(id );
}
}
/**
* unsubscribes a regular expression
*
* @return a boolean, true if the regexp existed, false otherwise or
* whenever an exception occured during unbinding
* @param String the string for the regular expression
*/
public boolean unBindMsg(String re) {
for (Enumeration e=regexp_out.keys();e.hasMoreElements();) {
Integer k = (Integer)e.nextElement();
if ( ((String)regexp_out.get(k)).compareTo(re) == 0) {
try {
unBindMsg(k.intValue());
} catch (IvyException ie) {
return false;
}
return true;
}
}
return false;
}
/**
* adds an application listener to a bus
* @param callback is an object implementing the IvyApplicationListener
* interface
* @return the id of the application listener, useful if you wish to remove
* it later
*/
public int addApplicationListener(IvyApplicationListener callback){
ivyApplicationListenerList.addElement(callback);
int id = ivyApplicationListenerList.indexOf( callback );
return id;
}
/**
* removes an application listener
* @param id the id of the application listener to remove
*/
public void removeApplicationListener(int id){
ivyApplicationListenerList.removeElementAt(id);
}
/* invokes the application listeners upon arrival of a new Ivy client */
public void connect(IvyClient client){
for ( int i = 0 ; i < ivyApplicationListenerList.size(); i++ ) {
((IvyApplicationListener)ivyApplicationListenerList.elementAt(i)).connect(client);
}
}
/* invokes the application listeners upon arrival of a new Ivy client */
void disconnectReceived(IvyClient client){
for ( int i = 0 ; i < ivyApplicationListenerList.size(); i++ ) {
((IvyApplicationListener)ivyApplicationListenerList.elementAt(i)).disconnect(client);
}
}
/*
* removes a client from the list
*/
void removeClient(IvyClient c) { clients.remove(c.getClientKey()); }
/**
* invokes the application listeners when we are summoned to die
* then stops
*/
public void dieReceived(IvyClient client, int id){
for ( int i=0 ;iivy<-- "+s); }
/* a small private method for debbugging purposes */
private String getClientNames() {
String s = appName+" clients are: ";
for (Enumeration e=clients.elements();e.hasMoreElements();){
s+=((IvyClient)e.nextElement()).getApplicationName()+" ";
}
return s;
}
public String domains(String toparse) {
String s="broadcasting on ";
Ivy.Domain[] d = parseDomains(toparse);
for (int index=0;index