package fr.dgac.ivy ; import java.lang.Thread; import java.net.*; import java.io.*; import java.util.*; import gnu.regexp.*; /** * A private Class for the the peers on the bus. * * @author François-Régis Colin * @author Yannick Jestin * @author http://www.tls.cena.fr/products/ivy/ * * each time a connexion is made with a remote peer, the regexp are exchanged * once ready, a ready message is sent, and then we can send messages, * die messages, direct messages, add or remove regexps, or quit. A thread is * created for each remote client. */ public class IvyClient extends Thread { /* the protocol magic numbers */ final static int Bye = 0; /* end of the peer */ final static int AddRegexp = 1;/* the peer adds a regexp */ final static int Msg = 2 ; /* the peer sends a message */ final static int Error = 3; /* error message */ final static int DelRegexp = 4;/* the peer removes one of his regex */ final static int EndRegexp = 5;/* no more regexp in the handshake */ final static int SchizoToken = 6; /* avoid race condition in concurrent connexions */ final static int DirectMsg = 7;/* the peer sends a direct message */ final static int Die = 8; /* the peer wants us to quit */ /* test pour jdk1.3 */ final static String StartArg = "\u0002";/* begin of arguments */ final static String EndArg = "\u0003"; /* end of arguments */ private static boolean debug = (System.getProperty("IVY_DEBUG")!=null) ; private Ivy bus; private Socket socket; private BufferedReader in; private OutputStream out; private Hashtable regexp_in = new Hashtable(); private Hashtable regexp_text = new Hashtable(); private String appName; private int appPort; private boolean gardefou=true; private boolean peerCalling; IvyClient(Ivy bus, Socket socket,boolean peerCalling) throws IOException { appName = "Unkown"; appPort = 0; this.bus = bus; this.socket = socket; this.peerCalling=peerCalling; in = new BufferedReader(new InputStreamReader(socket.getInputStream())); out = socket.getOutputStream(); Hashtable regexps=bus.regexp_out; // sends our ID, whether we initiated the connexion or not // the ID is the couple "host name,application Port", the host name // information is in the socket itself, the port is not known if we // initiate the connexion send(SchizoToken,bus.applicationPort,bus.appName); // sends our regexps to the peer for (Enumeration e = regexps.keys(); e.hasMoreElements(); ) { Integer key = (Integer)e.nextElement(); sendRegexp( key.intValue(),(String)regexps.get(key)); } send( EndRegexp,0,""); // spawns a thread to manage the incoming traffic on this // socket. We should be ready to receive messages now. start(); } String getApplicationName() { return appName ; } /** * allow an Ivy package class to access the list of regexps at a * given time. * perhaps we should implement a new IvyApplicationListener method to * allow the notification of regexp addition and deletion */ Enumeration getRegexps() { return regexp_text.elements(); } int getAppPort() { return appPort ; } /* perhaps we should perform some checking here */ void sendRegexp(int id,String regexp) {send(AddRegexp,id,regexp);} public void delRegexp(int id) {send( DelRegexp,id,"");} /** * sends the substrings of a message to the peer for each matching regexp. * @param message the string that will be match-tested * @return the number of messages sent to the peer */ int sendMsg( String message ) { int count = 0; for (Enumeration e = regexp_in.keys();e.hasMoreElements();) { Integer key = (Integer)e.nextElement(); RE regexp = (RE)regexp_in.get(key); REMatch result = regexp.getMatch(message); if ( result != null ) { // it's just to check if matching went right.... It is right. //String tmp=""; //for (int i=1;i<=regexp.getNumSubs();i++) { tmp+="'"+result.toString(i)+"' "; } //System.out.println(">> matching "+regexp.getNumSubs()+" blocks "+tmp); send(Msg,key,regexp.getNumSubs(),result); count++; } } return count; } /** * closes the connexion to the peer. * @param msg the debug information * the thread managing the socket is stopped */ void close(String msg) throws IOException { traceDebug( msg ); socket.close(); // TODO it seems it doesnt stop the thread out.close(); in.close(); gardefou=false; } /** * compares two peers the id is the couple (host,service port). * @param clnt the other peer * @return true if the peers are similir. This should not happen, it is bad * © ® (tm) */ boolean sameClient( IvyClient clnt ) { return ( appPort != 0 && appPort == clnt.appPort ) && ( getRemoteAddress() == clnt.getRemoteAddress() ) ; } /** * the code of the thread handling the incoming messages. * this thread stops (at least it should) when the socket is closed * or when gardefou=false */ public void run() { String msg = null; try { traceDebug("Connected from "+ socket.getInetAddress().getHostName()+ ":"+socket.getPort()); while ( gardefou && ((msg=in.readLine()) != null )) { int msgtype; // type of the last message received Integer msgid; // ID of the last message received RE regexp = null; // compiled regexp String token = null; /* * First stage: extract the message */ StringTokenizer st = new StringTokenizer(msg); if(!st.hasMoreTokens()){close("Bad format no type '"+msg+"'");break;} token=st.nextToken().trim(); if (token.length()==0){close("Bad format no type '"+msg+"'");break;} try { msgtype = Integer.parseInt(token); } catch ( NumberFormatException e ) { close("Bad format error parsing type'"+msg+"'"); break; } if(!st.hasMoreTokens()){close("Bad format no id '"+msg+"'");break;} token=st.nextToken(StartArg).trim(); try { msgid=Integer.valueOf(token); } catch ( NumberFormatException e ) { close("Bad format error parsing id '"+token+"'"); break; } String msgarg=""; // if (st.hasMoreTokens()) msgarg=st.nextToken("\n").trim(); if (st.hasMoreTokens()) msgarg=st.nextToken("\n"); // TODO: here is a bug ! // quick and dirty fix: I do the trimming by hand later on. // (cf ref: This method may be used to trim whitespace from the // beginning and end of a string; in fact, it trims all ASCII control characters as well. /* * second stage: process the message */ switch (msgtype) { case Bye: break; case AddRegexp: msgarg.trim(); if ( bus.CheckRegexp(msgarg) ) { try { regexp_in.put(msgid,new RE(msgarg)); regexp_text.put(msgid,msgarg); } catch (REException e) { System.err.println("Bad pattern: "+e.getMessage()); } } else { System.err.println( "Warning exp='"+msgarg+"' can't match removing from "+appName); } break; case DelRegexp: regexp_in.remove(msgid); regexp_text.remove(msgid); break; case EndRegexp: bus.connect(this); /* TODO * BUG ? the peer is perhaps not ready to handle this message * an assymetric processing should be written */ if (bus.ready_message!=null) sendMsg(bus.ready_message); break; case Msg: try { bus.callCallback(this,msgid,msgarg.substring(1,msgarg.length()-1)); } catch (IvyException ie) { // calling an inexistant callback System.err.println("calling an inexistant callback, the caller must be wrong !"); } break; case Error: traceDebug("Error msg "+msgid+" "+msgarg); break; case SchizoToken: appName=msgarg; appPort=msgid.intValue(); if ( bus.checkConnected(this) ) { close("Quitting Application already connected"); System.err.println("Rare ! A concurrent connect occured"); } break; case DirectMsg: msgarg.trim(); bus.directMessage( this, msgid.intValue(), msgarg ); break; case Die: bus.die( this,msgid.intValue()); break; default: System.err.println("*** IvyClient *** unhandled msg type "+ msgtype+" "+msgid+msgarg); break; } // switch } // while gardefou traceDebug("normally Disconnected from "+ socket.getInetAddress().getHostName()+":"+socket.getPort()); } catch (IOException e) { traceDebug("abnormally Disconnected from "+ socket.getInetAddress().getHostName()+":"+socket.getPort()); } bus.disconnect( this ); bus.removeClient( this ); } private void sendBuffer( String buffer ) { buffer += "\n"; try { out.write(buffer.getBytes() ); out.flush(); } catch ( IOException e ) { /* * TODO * we should throw an exception here */ System.err.println("IvyClient.sendBuffer.write failed. FIX ME"); } } private void send(int type, int id, String arg) { sendBuffer(type+" "+id+StartArg+arg); } private void send(int type, Integer id, int nbsub, REMatch result) { String buffer = type+" "+id+StartArg; // Start at 1 because group 0 represent entire matching for(int sub = 1; sub <= nbsub; sub++) { if (result.getStartIndex(sub) > -1) { buffer += result.toString(sub)+EndArg; } } sendBuffer(buffer); } private void sendDie() {send(Die,0,"");} private void sendDie(String message) {send(Die,0,message);} private InetAddress getRemoteAddress() { return socket.getInetAddress(); } private void traceDebug(String s){ if (debug) System.out.println("-->IvyClient<-- "+s); } } // class IvyClient /* EOF */