home *** CD-ROM | disk | FTP | other *** search
Java Source | 1996-08-14 | 21.3 KB | 909 lines |
- /*
- * @(#)ComObjIRC.java 1.0 96/03/03 Ulrich Gall & Jan Kautz
- *
- * Copyright (c) 1996 Ulrich Gall & Jan Kautz
- * uhgall@cip.informatik.uni-erlangen.de
- * jnkautz@cip.informatik.uni-erlangen.de
- * Hofmannstr. 48, D-91052 Erlangen, Germany, Fax: +49-9131-201358
- *
- */
-
- package como.irc;
-
- import java.io.*;
- import java.util.*;
- import java.awt.*;
- import java.applet.*;
- import java.net.*;
-
- import como.awt.*;
- import como.commlet.*;
- import como.util.*;
- import como.sys.*;
-
- public class ComObjIRC implements ComObj {
- private Hashtable involvedusers;
- private Hashtable allowedusers;
- private Hashtable usersleft;
- private Vector cache;
- private ServerIRC server;
- private Commlet commlet = null;
- private String commletname = "not_set.class";
- private String topic = "No topic set!";
- private Thread readthread;
- private CubbyHole cubbyhole;
- private IrcChan chan;
- private IrcSocket socket;
- private int myID;
- private int masterID;
- private boolean masterflag = false;
- protected boolean do_not_get_any_more_messages = false;
- private boolean i_am_not_allowed = false;
-
- /**
- * Start ComObjIRC. Establish Connection to given Channel.
- * Set up ReadThread for it.
- */
- ComObjIRC( ServerIRC s, IrcChan c, User ego, boolean iammaster ) throws IOException {
- chan = c;
- server = s;
- masterID = -1;
- masterflag = iammaster;
-
- involvedusers = new Hashtable();
- allowedusers = new Hashtable();
- usersleft = new Hashtable();
- cache = new Vector();
-
- // create cubbyhole for answer messages
- cubbyhole = new CubbyHole();
-
- // open connection to ircd on the server
- // and join the channel chan.ircName()
- try {
- socket = new IrcSocket( server, chan );
- } catch( IOException e ) {
- // this means to me: leave
- throw new IOException();
- }
-
- topic = chan.topic;
-
- /* this here is really ugly, but what shall I do else? */
- ego.put( User.NICK, socket.nick );
- myID = calcHashCode( ego );
- ego.put( User.ID, new Integer(myID) );
-
- involvedusers.put( new Integer(myID), ego );
-
- if( iammaster ) masterID = myID;
-
- // start my ReadThread, which reads from the given socket
- readthread = new ReadThreadIRC( this, socket );
- readthread.start();
- }
-
-
- /**
- * set the Name of the commlet
- */
- synchronized public void setCommletName( String name ) {
- commletname = name;
- }
-
- /**
- * Tell the ComObj about his commlet. This is necessary this way
- * because the ComObj is instantiated before the commlet
- */
- synchronized public void setCommlet( Commlet c ) {
- commlet = c;
- }
-
- /**
- * return the Commlet
- */
- public Commlet getCommlet() {
- return commlet;
- }
-
- /**
- * returns the master ID.
- * -1 means it is still unknown.
- */
- synchronized public int getMasterID() {
- return masterID;
- }
-
- /**
- * returns my ID.
- * -1 means it is still unknown (I didn't login myself to this commlet/comobj).
- */
- public int getMyID() {
- return myID;
- }
-
- /**
- * return true if i am the master
- */
- synchronized public boolean iAmMaster() {
- return masterflag;
- }
-
- /**
- * set a new topic (in the irc-channel) and tell others
- * about it.
- */
- synchronized public void setNewTopic( String topic ) {
- this.topic = topic;
- socket.send( "TOPIC #"+chan.ircName()+" :"+topic, true );
- sendToAll( new Msg( Msg.NEW_TOPIC, topic ) );
- }
-
- /**
- * It will set the which-User-attributes to the newuser-attributes,
- * paying attention to those attributes, who may not be altered.
- */
- synchronized private void setUser( User which, User newuser ) {
- Enumeration e = newuser.keys();
- while( e.hasMoreElements() )
- {
- Object key;
- Object elem;
- Integer i;
-
- key = e.nextElement();
- elem = newuser.get( key );
- if( key instanceof Integer )
- {
- i = (Integer)key;
-
- // things that I don't want to be overwritten
- if( i.equals( User.NICK ) || i.equals( User.COMOUSER ) ||
- i.equals( User.SOCKET ) || i.equals( User.ID ) ) continue;
- }
-
- which.put( key, elem );
- }
- }
-
- /**
- * Change some attributes of the local user.
- * Some can't be changed (NICK, SOCKET, ID,...).
- */
- synchronized public void setLocalUser( User user ) {
- setUser( getUser( getMyID() ), user );
- sendToAll( new Msg( Msg.NEW_USER_INFO, getUser( getMyID() ) ) );
- }
-
- /**
- * return the User-information of the user with id.
- */
- synchronized public User getUser( int id ) {
- // TODO: get more information about him!!!
- // ask himself, perhaps
- // no dont ask. he will tell us automatically
-
- User user = (User)involvedusers.get( new Integer( id ) );
-
- // now let's try to find him in userleft
- if( user == null ) {
- user = (User)usersleft.get( new Integer( id ) );
- }
-
- return user;
- }
-
- /**
- * return the Username of the user with id.
- */
- synchronized public String getUserName( int id ) {
- if( id == -1 ) {
- return "NoName";
- } else {
- User user = getUser( id );
- return (String)user.get( User.NAME );
- }
- }
-
- /**
- * return a Vector of all involved Users
- */
- synchronized public Vector getUsers() {
- Vector v = new Vector();
- Enumeration e = involvedusers.elements();
- User me;
-
- // I want to have me as the first in the list!
-
- me = getUser( getMyID() );
- if( me == null ) return v;
-
- v.addElement( me.clone() );
-
- while( e.hasMoreElements() ) {
- User u = (User)e.nextElement();
-
- if( me == u ) continue; // I am already in there
-
- u = (User)u.clone(); // don't show him the real and only
- v.addElement( u );
- }
-
- return v;
- }
-
- /**
- * Send Msg to msg.to
- */
- synchronized public void sendTo( Msg msg ) {
- User user = getUser( msg.to );
- msg.from = getMyID();
-
- /* Well this is a bad thing here:
- If I call it like this, the handleMsg()
- may call again sendTo() which may call again
- handleMsg()... Then the handleMsg() can never
- be synchronized!!!
-
- if( msg.to == getMyID() )
- commlet.handleMsg( msg );
- else
- */
-
- {
- if( user == null )
- Debug.msg( 45, "ComObjIRC.sendTo(): unknown user "+msg.to );
- else
- sendTo( user, msg );
- }
- }
-
- /**
- * Send Message to User user
- */
- synchronized private void sendTo( User user, Msg msg ) {
- msg.from = getMyID();
- socket.writePrivMsg( user, msg );
- }
-
- /**
- * Send Msg to all users of this session except myself.
- */
- synchronized public void sendToOthers( Msg msg ) {
- msg.from = getMyID();
- msg.to = 0;
- socket.writeMsg( msg );
- }
-
- /**
- * Send Msg to all users of this session including myself.
- */
- synchronized public void sendToAll( Msg msg ) {
- // to the others
- sendToOthers( msg );
-
-
- // to myself
- msg.to = msg.from = getMyID();
- sendTo( msg );
-
- // If you wonder why we are sending
- // even my own things to the IRC-Server:
- // We think it is necessary, that Messages
- // that are sent to everyone by e.g. two users
- // should arrive in the same order everywhere!
- // This can only be guaranteed, if we send
- // it back to the IRC-Server.
- // If you only have commlets, that don't insist
- // on this, you could use:
- // commlet.handleMsg( msg );
- // But then you have to make a Thread for it, because
- // it could block everything otherwise!!!
- }
-
- /**
- * Send Msg to group of users of this session.
- */
- synchronized public void sendToGroup( int to[], Msg msg ) {
- int len = to.length;
-
- for( int i = 0; i < len; i++ ) {
- msg.to = to[i];
- sendTo( msg );
- }
- }
-
- /**
- * ask msg.to msg. Then wait for a return-msg. The type
- * of the return msg must be -msg.type of the question!!
- */
- public synchronized Msg ask( Msg msg ) {
- sendTo( msg );
-
- // answer must be: -Msg.type !!!
- // else I will wait until doom's day!
-
- return (Msg)cubbyhole.get();
- }
-
-
- /**
- * kickUser kicks a User out of this channel!
- * It send him a KICK_USER message!
- */
- synchronized public void kickUser( int id, String reason ) {
- sendTo( new Msg( Msg.KICK_USER, getMyID(), id, reason ) );
- }
-
- /**
- * This method is invoked when a user left. It is called in every
- * ComObj belonging to this communication. In every comobj it WILL
- * choose the same User as master!
- * This is done by choosing the user with the lowest ID.
- */
- synchronized private void chooseNewMaster() {
- Enumeration e = involvedusers.elements();
- int lowest_id = -1; // ids in ComObjIRC are always positive
-
- while( e.hasMoreElements() ) {
- User u = (User)e.nextElement();
- int uid = ((Integer)u.get( User.ID )).intValue();
-
- if( lowest_id == -1 ) {
- lowest_id = uid;
- }
- else {
- if( uid < lowest_id )
- lowest_id = uid;
- }
- }
-
- masterID = lowest_id; // now he is the new master
- if( masterID == getMyID() ) {
- masterflag = true;
- }
- }
-
-
- /**
- * Here we can handle msg for the ComObj (or messages
- * important for the ComObj)
- */
- synchronized public Msg preHandleMsg( Msg msg ) {
- User user = null;
-
- switch( msg.type ) {
- Integer userid;
- int id;
-
- case Msg.INVITATION:
- // This message is for the server!
- server.handleMsg( msg );
- return null;
-
- case Msg.KICK_USER:
- String reason = (String)msg.arg;
- Panel msgpanel = new Panel();
- if( reason == null ) reason = " ";
- msgpanel.setLayout( new VertLayout( VertLayout.STRETCH ) );
- msgpanel.add( new Label( "Sorry, you've been kicked out of the Channel" ) );
- msgpanel.add( new Label( "by "+getUserName( msg.from )+"!" ) );
- msgpanel.add( new Label( "Reason: "+(String)msg.arg ) );
-
- new SmartFrame( msgpanel, "OK" );
- destroy();
-
- // tell thread to stop now (will also be stopped by
- // comobj.logout(), called in getCommlet().stop()
- return msg;
-
- case Msg.NOT_ALLOWED:
- new SmartFrame( "Sorry, you are not allowed to join! Please quit your Commlet!" );
-
- i_am_not_allowed = true;
- sendPartMessage();
-
- // I wanted to quit it automatically, but this
- // does not work, because the message NOT_ALLOWED
- // comes before everything is initialized!
- // So don't do this. -> Let the user quit manually.
- // destroy();
-
- // tell thread to stop now! This is at least one
- // thing i can do, get no more incoming messages!
- return msg;
-
- case Msg.LINE_DROPPED:
- // Tell the commlet, that it must quit now!
-
- new SmartFrame( "Sorry, line to server dropped!" );
- destroy();
-
- // Sorry this is a special case :-(
- // Thread must know, that he must stop
- return msg;
-
- case Msg.USER_LEFT:
- String nick = (String)msg.arg;
- Enumeration e = involvedusers.keys();
- Integer testid;
-
- userid = new Integer( -1 );
-
- // search the nick in the involveduser-List
- // and get his ID
- while( e.hasMoreElements() )
- {
- testid = (Integer)e.nextElement();
- user = (User)involvedusers.get( testid );
- if( ((String)user.get( User.NICK )).compareTo( nick ) == 0 )
- {
- userid = testid;
- break;
- }
- }
- if( userid.intValue() == -1 )
- {
- Debug.msg( 43, "ComObjIRC.preHandleMsg(USER_LEFT): Unknown User "+nick+" left" );
- return null;
- }
-
- // allowedusers.remove() should not be necessary here
- // user should have been removed already (in addUser())
- allowedusers.remove( userid );
- involvedusers.remove( userid );
-
- usersleft.put( userid, user );
-
- // was the user who the master? yes??
- // then choose a new one. just take the user
- // with the lowest ID. then tell the commlet
- chooseNewMaster();
- commlet.handleMsg( new Msg( Msg.NEW_MASTER, getMasterID(), getMyID(), new Integer(getMasterID()) ) );
-
- // now tell the commlet, that a user left.
- // make sure, that if he/she calls getUsers() the leaving user
- // is not in that list! But if he/she calls getUser(userid)
- // he/she still gets information about him/her!
- commlet.handleMsg( new Msg( Msg.USER_LEFT, userid.intValue(), getMyID(), userid ) );
-
- // TODO: remove it here ?
- // or always remember 10 users, or ...
- // But it seems ok like this....
- // perhaps sometimes I should change this!
- usersleft.remove( userid );
- return null;
-
- case Msg.ADD_USER:
- User who = (User)msg.arg;
-
- if( (id = loginUser( who )) > 0 )
- addUser( id );
- else {
- // well if i am the master tell him to leave again!
-
- if( iAmMaster() )
- sendTo( who, new Msg( Msg.NOT_ALLOWED ) );
- }
- return null;
-
- case Msg.GET_USER_INFO:
- Msg retmsg;
-
- // oh, I'm still not here
- if( getMyID() == -1 ) {
- cacheMsg( msg );
- return null;
- }
-
- user = getUser( getMyID() );
-
- // construct answer message for that query
- retmsg = new Msg( Msg.NEW_USER_INFO, 0, msg.from, user );
- sendTo( retmsg );
- return null;
-
- case Msg.NEW_USER_INFO:
- User newuser = (User)msg.arg, olduser;
- userid = new Integer(msg.from);
-
- olduser = (User)involvedusers.get( userid );
- if( olduser != null ) {
- // Attention here:
- // give him the old user info
- // he has to get the new via getUser()
- msg.arg = olduser.clone();
-
- // set the olduser correctly
- setUser( olduser, newuser );
-
- return msg;
- } else {
- cacheMsg( msg );
- return null;
- }
-
- case Msg.NEW_MASTER:
- Integer i = (Integer)msg.arg;
-
- if( (User)involvedusers.get( i ) == null ) {
- cacheMsg( msg );
- return null;
- }
-
- if( masterflag == true )
- Debug.msg( 55, "ComObjIRC().preHandleMsg().NEW_MASTER: impossible message" );
-
- masterID = i.intValue();
-
- if( masterID == getMyID() ) masterflag = true;
-
- // well nice to know this, but the commlet also
- // wants to know it!
- return msg;
-
- case Msg.NEW_TOPIC:
-
- if( involvedusers.get( new Integer( msg.from ) ) == null ) {
- cacheMsg( msg );
- return null;
- }
- else {
- topic = (String)msg.arg;
- return msg;
- }
-
- case Msg.NO_DATA:
- return null;
-
- default:
- if( involvedusers.get( new Integer( msg.from ) ) == null )
- {
- cacheMsg( msg );
- return null;
- }
-
- if( msg.isAnswer() ) // It is a answer for something!!
- {
- cubbyhole.put( msg );
- return null;
- }
-
- return msg;
- }
- }
-
- /**
- * Here I can cache messages. This is usefull, if I get a message
- * from a user that I don't know yet.
- */
- synchronized private void cacheMsg( Msg msg ) {
- long mil = System.currentTimeMillis(); // current time in milliseconds since 1970
- Long millis = new Long( mil );
-
- Vector elem = new Vector();
- elem.addElement( millis );
- elem.addElement( msg );
-
- cache.addElement( elem );
- }
-
- /**
- * Search the cache if I have messages from the specified user,
- * if true, then deliver it (i.e. commlet.handleMsg())
- * else, keep the messages.
- * This is called, when a user is added.
- */
- synchronized private void handleCachedMsg( int id ) {
- Enumeration e = cache.elements();
-
- while( e.hasMoreElements() ) {
- Vector elem = (Vector)e.nextElement();
- Msg msg = (Msg)elem.elementAt( 1 );
-
- if( msg.from == id ) {
- // aaah, it's from her or him
-
- cache.removeElement( elem );
-
- // I have to ask for a new enumeration!!!
- // Removing an element causes the
- // Enumeration to stop an element earlier
- // than it should (look at java/util/Vector.java)
- e = cache.elements();
-
- if( preHandleMsg( msg ) != null )
- {
- // ok it's a message for the commlet
-
- // well i have to set the 'to' here, because it could
- // be zero (equals to all)
- msg.to = getMyID();
- commlet.handleMsg( msg );
- }
- }
- }
- }
-
- /**
- * Search the cache if I have messages older than 60 seconds;
- * if true delete them.
- */
- synchronized protected void removeOldCachedMsg() {
- Enumeration e = cache.elements();
-
- while( e.hasMoreElements() ) {
- Vector elem = (Vector)e.nextElement();
- Long millis = (Long)elem.elementAt(0);
-
- if( millis.longValue() < (System.currentTimeMillis() - (60 * 10 * 1000)) ) {
- // aaah, it's too old
-
- Msg msg = (Msg)elem.elementAt( 1 );
-
- cache.removeElement( elem );
- }
- }
- }
-
- /**
- * Asks commlet if the User is admitted to enter.
- */
- synchronized public int loginUser( User user ) {
-
- // let's see if I was permitted to join the commlet
- // if not, don't add any users.
- if( i_am_not_allowed ) return -1;
-
- if( commlet.isUserAdmitted( user ) ) {
- int id = calcHashCode( user );
-
- allowedusers.put( new Integer(id), user );
- return id;
- }
- else
- return -1;
- }
-
- /**
- * Adds a user who has already logged in.
- * It tells this user, who i am and if i am the master
- */
- synchronized public void addUser( int userid ) {
- User user;
-
- if( (user = (User)allowedusers.get( new Integer(userid) )) != null )
- {
- user.put( User.ID, new Integer(userid) );
- involvedusers.put( new Integer(userid), user );
- allowedusers.remove( new Integer(userid) );
- commlet.handleMsg( new Msg( Msg.ADD_USER, new Integer( userid ) ) );
-
- // now handle the cached messages
- handleCachedMsg( userid );
-
- // now tell her/him who i am.
- sendTo( new Msg( Msg.NEW_USER_INFO, getMyID(), userid, getUser( getMyID() ) ) );
-
- // if i am master tell him/her
- // and tell her the topic of this channel
- if( iAmMaster() )
- {
- sendTo( new Msg( Msg.NEW_MASTER, getMyID(), userid, new Integer(getMyID()) ) );
- sendTo( new Msg( Msg.NEW_TOPIC, getMyID(), userid, topic ) );
- }
- }
- else
- Debug.msg( 98, "Not allowed user called ComObj.addUser()" );
- return;
- }
-
- /**
- * Add myself to this Commlet.
- * If i am master then set the topic
- */
- synchronized public void addMe( User ego ) {
- commlet.handleMsg( new Msg( Msg.ADD_USER, new Integer(myID) ) );
-
- if( iAmMaster() )
- {
- setNewTopic( topic );
- }
- }
-
- /**
- * Calc a HashCode out of a user. Avoids same hashcode for
- * different nick names.
- */
- synchronized private int calcHashCode( User user ) {
- String nick = (String)user.get( User.NICK );
- int id = nick.hashCode();
-
- if( id < 0 ) id = -id;
-
- // as long as the id is already used !
- while( involvedusers.containsKey( new Integer(id) ) ||
- allowedusers.containsKey( new Integer(id) ) )
- id++;
-
- return id;
- }
-
- synchronized private void sendPartMessage() {
- try {
- socket.send( "PART #"+chan.ircName(), true );
- } catch( Exception e ) {
- Debug.msg( 75, "ComObjIRC.logout(): Couldn't send PART-Message anymore" );
- }
- }
-
- /**
- * This is called in order to quit the ComObj.
- * It also tells the server, that i quitted.
- */
- synchronized public void logout() {
- do_not_get_any_more_messages = true;
-
- sendPartMessage();
-
- try {
- socket.close();
- } catch( Exception e ) {
- // noone cares anymore here!
- Debug.msg( 75, "ComObjIRC.logout(): Couldn't close socket." );
- }
-
- // I have to stop the read-thread after I quitted the IRC.
- // The other way I had problems to close the socket.
- // Don't ask me why!
-
- if( readthread != null && Thread.currentThread() != readthread )
- {
- // stop the readthread. then it is not possible anymore
- // to get message!
- readthread.stop();
- readthread = null;
-
- }
-
- server.loggedout( this, chan );
- }
-
- /**
- * Destroy everything in here. I.e. also quit the commlet.
- * It will also call logout().
- */
- public void destroy() {
- commlet.stop();
- // stops the commlet. the commlet calls
- // ComObjIRC.logout() and that disconnects
- }
-
- /**
- * opens an URL to your DocumentHost
- */
- private URL getDataURL( String append ) throws MalformedURLException {
- Applet a = server.getApplet();
- URL url = new URL(a.getDocumentBase(),server.PATH_COMMLET+commletname+"/"+append );
-
- return url;
- }
-
- /**
- * Load a picture with the specified filename!
- * It will be loaded from the BaseDocument's http-server.
- */
- public Image loadImage( String filename ) {
- Applet a = server.getApplet();
- if( a == null ) return null;
-
- try {
- URL url = getDataURL( filename );
- return a.getImage( url );
- } catch( Exception e ) {
- return null;
- }
- }
-
- /**
- * Load an audioclip with the specified filename!
- */
- public AudioClip loadAudioClip( String filename ) {
- Applet a = server.getApplet();
- if( a == null ) return null;
-
- try {
- URL url = getDataURL( filename );
- return a.getAudioClip( url );
- } catch( Exception e ) {
- return null;
- }
- }
-
- /**
- * Open an input-stream to the specified file!
- */
- public InputStream openInputStream( String filename ) {
- Applet a = server.getApplet();
- if( a == null ) return null;
-
- try {
- URL url = getDataURL( filename );
- return url.openStream();
- } catch( Exception e ) {
- return null;
- }
- }
-
- public String toString() {
- return "ComObjIRC for #"+chan.ircName();
- }
- }
-
- class ReadThreadIRC extends Thread {
- ComObjIRC comobj;
- IrcSocket socket;
-
- ReadThreadIRC( ComObjIRC co, IrcSocket socket ) {
- comobj = co;
- this.socket = socket;
- }
-
- public void run() {
- Msg msg;
-
- // ah well. it can happen that I don't know yet what my commlet is
- // this means to me: wait for it.
- while( true ) {
- if( comobj.getCommlet() != null ) break;
- try {
- Thread.sleep( 100 );
- } catch( InterruptedException e ) { }
- }
-
- while( true )
- {
- msg = readMsg();
-
- if( msg == null )
- continue; // already handled
-
- if( msg.type == Msg.LINE_DROPPED || msg.type == Msg.NOT_ALLOWED ||
- msg.type == Msg.KICK_USER ) {
- // Sorry, this is a special case :(
- // We must stop this thread now!
- break;
- }
-
- msg.to = comobj.getMyID();
- comobj.getCommlet().handleMsg(msg);
- }
- }
-
- /**
- * read a Msg from my socket. Handle Messages for the ComObj,
- * before the Commlet gets it.
- */
- Msg readMsg() {
- Msg msg;
-
- msg = socket.readMsg();
-
- // that is when we are logging out!
- // tell the read thread to stop.
- if( comobj.do_not_get_any_more_messages == true )
- return new Msg( Msg.LINE_DROPPED );
-
- // here let's delete old cached messages!
- // it's not important where/when I do it, but it
- // has to be done
- comobj.removeOldCachedMsg();
-
- // Here we handle private Message, not
- // to be used by the commlet
- // returns null if msg was handled
- // else: returns the orginal msg
- return comobj.preHandleMsg( msg );
- }
- }
-