summaryrefslogtreecommitdiffstats
path: root/EssentialsUpdate/src/f00f/net/irc/martyr/IRCConnection.java
diff options
context:
space:
mode:
Diffstat (limited to 'EssentialsUpdate/src/f00f/net/irc/martyr/IRCConnection.java')
-rw-r--r--EssentialsUpdate/src/f00f/net/irc/martyr/IRCConnection.java1163
1 files changed, 0 insertions, 1163 deletions
diff --git a/EssentialsUpdate/src/f00f/net/irc/martyr/IRCConnection.java b/EssentialsUpdate/src/f00f/net/irc/martyr/IRCConnection.java
deleted file mode 100644
index 159f533cd..000000000
--- a/EssentialsUpdate/src/f00f/net/irc/martyr/IRCConnection.java
+++ /dev/null
@@ -1,1163 +0,0 @@
-/*
- * IRCConnection.java
- *
- * Copyright (C) 2000, 2001, 2002, 2003, 2004 Ben Damm
- *
- * This library 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 library 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.
- *
- * See: http://www.fsf.org/copyleft/lesser.txt
- */
-
-package f00f.net.irc.martyr;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.util.LinkedList;
-import java.util.Observer;
-import java.util.StringTokenizer;
-
-import f00f.net.irc.martyr.clientstate.ClientState;
-import f00f.net.irc.martyr.commands.UnknownCommand;
-import f00f.net.irc.martyr.errors.UnknownError;
-import f00f.net.irc.martyr.replies.UnknownReply;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-// TODO:
-//
-// Add synchronous disconnect.
-//
-/**
- * <p><code>IRCConnection</code> is the core class for Martyr.
- * <code>IRCConnection</code> manages the socket, giving commands to the server
- * and passing results to the parse engine. It manages passing information out
- * to the application via the command listeners and state listeners.
- * <code>IRCConnection</code> has no IRC intelligence of its own, that is left
- * up to the classes on the command and state listener lists. A number of
- * listeners that do various tasks are provided as part of the framework.</p>
- *
- * <p><b>Please read this entirely before using the framework.</b> Or
- * what the heck, try out the example below and see if it works for ya.</p>
- *
- * <h2>States and State Listeners</h2>
- * <p><code>IRCConnection</code> is always in one of three states.
- * <code>UNCONNECTED</code>, <code>UNREGISTERED</code> or
- * <code>REGISTERED</code>. It keeps a list of listeners that
- * would like to know when a state change occurs. When a state change
- * occurs each listener in the list, in the order they were added, is
- * notified. If a listener early up on the list causes something to happen
- * that changes the state before your listener gets notified, you will be
- * notified of the state change even though the state has changed. You
- * will be notified again of the new state. That is, state change
- * notifications will always be in order, but they may not always reflect
- * the "current" state.</p>
- *
- * <h2>Commands and Command Listeners</h2>
- * <p><code>IRCConnection</code> also keeps a list of listeners for
- * when a command arrives from the server. When a command arrives, it
- * is first parsed into an object. That object is then passed around
- * to all the listeners, again, in order. Commands can be received
- * and the socket closed before the commands are actually send to the
- * listeners, so beware that even though you receive a command, you
- * may not always be guaranteed to have an open socket to send a
- * response back on. A consumer of the command should never modify
- * the command object. If you try to send a command to a closed
- * socket, <code>IRCConnection</code> will silently ignore your
- * command. Commands should always be received in order by all
- * listeners, even if a listener higher up in the list sends a
- * response to the server which elicits a response back from the
- * server before you've been told of the first command.</p>
- *
- * <h2>Connecting and staying connected</h2>
- * <p>The AutoReconnect class can connect you and will try to stay
- * connected. Using AutoReconnect to connect the
- * first time is recommended, use the <code>go(server,port)</code> method once
- * you are ready to start.</p>
- *
- * <h2>Registration On The Network</h2>
- * <p>The AutoRegister class can register you automatically on the
- * network. Otherwise, registration is left up to the consumer.
- * Registration should occur any time the state changes to
- * <code>UNREGISTERED</code>. The consumer will know this because it
- * has registered some class as a state observer.
- * </p>
- *
- * <h2>Auto Response</h2>
- * <p>Some commands, such as Ping require an automatic response.
- * Commands that fall into this category can be handled by the
- * <code>AutoResponder</code> class. For a list of what commands
- * <code>AutoResponder</code> auto responds to, see the source.</p>
- *
- * <h2>Joining and Staying Joined</h2>
- * <p>You can use the <code>AutoJoin</code> class to join a channel
- * and stay there. <code>AutoJoin</code> will try to re-join if
- * kicked or if the connection is lost and the server re-connects.
- * <code>AutoJoin</code> can be used any time a join is desired. If
- * the server is not connected, it will wait until the server
- * connects. If the server is connected, it will try to join right
- * away.</p>
- *
- * <h2>Example Usage</h2>
- * <p>You will probably want to at least use the
- * <code>AutoRegister</code> and <code>AutoResponder</code> classes.
- * Example:</p>
- *
- * <p>Note that in the example, the first line is optional.
- * <code>IRCConnection</code> can be called with its default
- * constructor. See note below about why this is done.
- * <code>IRCConnection</code> will instantiate its own
- * <code>ClientState</code> object if you do not provide one.</p>
- *
- * <pre>
- * ClientState clientState = new MyAppClientState();
- * IRCConnection connection = new IRCConnection( clientState );
- *
- * // AutoRegister and AutoResponder both add themselves to the
- * // appropriate observerables. Both will remove themselves with the
- * // disable() method.
- *
- * AutoRegister autoReg
- * = new AutoRegister( "repp", "bdamm", "Ben Damm", connection );
- * AutoReconnect autoRecon = new AutoReconnect( connection );
- * AutoResponder autoRes = new AutoResponder( connection );
- *
- * // Very important that the objects above get added before the connect.
- * // If done otherwise, AutoRegister will throw an
- * // IllegalStateException, as AutoRegister can't catch the
- * // state switch to UNREGISTERED from UNCONNECTED.
- *
- * autoRecon.go( server, port );
- * </pre>
- *
- * <h2>Client State</h2>
- * <p>The <code>ClientStateMonitor</code> class tells commands to
- * change the client state when they are received.
- * <code>ClientStateMonitor</code> is automatically added to the
- * command queue before any other command, so that you can be
- * guaranteed that the <code>ClientState</code> is updated before any
- * observer sees a command.</p>
- *
- * <p>So, how does an application know when a channel has been joined,
- * a user has joined a channel we are already on, etc? How does the
- * application get fine-grained access to client state change info?
- * This is a tricky question, and the current solution is to sublcass
- * the <code>clientstate.ClientState</code> and
- * <code>clientstate.Channel</code> classes with your own, overriding
- * the <code>setXxxxXxxx</code> methods. Each method would call
- * <code>super.setXxxXxxx</code> and then proceed to change the
- * application as required.</p>
- *
- * <h2>Startup</h2>
- * <p>IRCConnection starts in the <code>UNCONNECTED</code> state and
- * makes no attempt to connect until the <code>connect(...)</code>
- * method is called.</p>
- *
- * <p><code>IRCConnection</code> starts a single thread at
- * construction time. This thread simply waits for events. An event
- * is a disconnection request or an incoming message. Events are
- * dealt with by this thread. If connect is called, a second thread
- * is created to listen for input from the server (InputHandler).
- *
- * @see f00f.net.irc.martyr.A_FAQ
- * @see f00f.net.irc.martyr.clientstate.ClientState
- * @see f00f.net.irc.martyr.services.AutoRegister
- * @see f00f.net.irc.martyr.services.AutoResponder
- * @see f00f.net.irc.martyr.State
- *
- */
-/*
- * Event handling re-org
- *
- * - A message is an event
- * - A disconnect request is an event, placed on the queue?
- * -- Off I go to do other stuff.
- */
-public class IRCConnection {
- static Logger log = Logger.getLogger(IRCConnection.class.getName());
-
- public IRCConnection()
- {
- this( new ClientState() );
- }
-
- public IRCConnection( ClientState clientState )
- {
- // State observers are notified of state changes.
- // Command observers are sent a copy of each message that arrives.
- stateObservers = new StateObserver();
- commandObservers = new CommandObserver();
- this.clientState = clientState;
- stateQueue = new LinkedList<State>();
-
- commandRegister = new CommandRegister();
- commandSender = new DefaultCommandSender();
-
- setState( State.UNCONNECTED );
-
- new ClientStateMonitor( this );
-
- localEventQueue = new LinkedList<String>();
-
- eventThread = new EventThread();
- eventThread.setDaemon( true );
- startEventThread();
- }
-
- /**
- * This method exists so that subclasses may perform operations before
- * the event thread starts, but overriding this method.
- * */
- protected void startEventThread()
- {
- eventThread.start();
- }
-
- /**
- * In the event you want to stop martyr, call this. This asks the
- * event thread to finish the current event, then die.
- * */
- public void stop()
- {
- eventThread.doStop();
- }
-
- /**
- * Performs a standard connection to the server and port. If we are already
- * connected, this just returns.
- *
- * @param server Server to connect to
- * @param port Port to connect to
- * @throws IOException if we could not connect
- */
- public void connect( String server, int port )
- throws IOException
- {
- synchronized( connectMonitor )
- {
- //log.debug("IRCConnection: Connecting to " + server + ":" + port);
- if( connected )
- {
- log.severe("IRCConnection: Connect requested, but we are already connected!");
- return;
- }
-
- connectUnsafe( new Socket( server, port ), server );
- }
- }
-
- /**
- * This allows the developer to provide a pre-connected socket, ready for use.
- * This is so that any options that the developer wants to set on the socket
- * can be set. The server parameter is passed in, rather than using the
- * customSocket.getInetAddr() because a DNS lookup may be undesirable. Thus,
- * the canonical server name, whatever that is, should be provided. This is
- * then passed on to the client state.
- *
- * @param customSocket Custom socket that we will connect over
- * @param server Server to connect to
- * @throws IOException if we could not connect
- * @throws IllegalStateException if we are already connected.
- */
- public void connect( Socket customSocket, String server )
- throws IOException, IllegalStateException
- {
- synchronized( connectMonitor )
- {
- if( connected )
- {
- throw new IllegalStateException( "Connect requested, but we are already connected!" );
- }
-
- connectUnsafe( customSocket, server );
- }
-
- }
-
- /**
- * <p>Orders the socket to disconnect. This doesn't actually disconnect, it
- * merely schedules an event to disconnect. This way, pending incoming
- * messages may be processed before a disconnect actually occurs.</p>
- * <p>No errors are possible from the disconnect. If you try to disconnect an
- * unconnected socket, your disconnect request will be silently ignored.</p>
- */
- public void disconnect()
- {
- synchronized( eventMonitor )
- {
- disconnectPending = true;
- eventMonitor.notifyAll();
- }
- }
-
- /**
- * Sets the daemon status on the threads that <code>IRCConnection</code>
- * creates. Default is true, that is, new InputHandler threads are
- * daemon threads, although the event thread is always a daemon. The
- * result is that as long as there is an active connection, the
- * program will keep running.
- *
- * @param daemon Set if we are to be treated like a daemon
- */
- public void setDaemon( boolean daemon )
- {
- this.daemon = daemon;
- }
-
- /**
- * Signal threads to stop, and wait for them to do so.
- * @param timeout *2 msec to wait at most for stop.
- *
- * */
- public void shutdown(long timeout)
- {
- // Note: UNTESTED!
- try
- {
- // 1) shut down the input thread.
- synchronized( inputHandlerMonitor )
- {
- if( inputHandler != null )
- {
- inputHandler.signalShutdown();
- }
- synchronized( socketMonitor )
- {
- if( socket != null )
- {
- try
- {
- socket.close();
- }
- catch (IOException e)
- {
- // surprising?
- }
- }
- }
- if( inputHandler != null )
- {
- inputHandler.join(timeout);
- }
- }
-
- // 2) shut down the event thread.
- eventThread.shutdown();
- eventThread.join(timeout);
- }
- catch( InterruptedException ie )
- {
- // We got interrupted - while waiting for death.
- // Shame that.
- }
- }
-
- public String toString()
- {
- return "IRCConnection";
- }
-
- public void addStateObserver( Observer observer )
- {
- //log.debug("IRCConnection: Added state observer " + observer);
- stateObservers.addObserver( observer );
- }
-
- public void removeStateObserver( Observer observer )
- {
- //log.debug("IRCConnection: Removed state observer " + observer);
- stateObservers.deleteObserver( observer );
- }
-
- public void addCommandObserver( Observer observer )
- {
- //log.debug("IRCConnection: Added command observer " + observer);
- commandObservers.addObserver( observer );
- }
-
- public void removeCommandObserver( Observer observer )
- {
- //log.debug("IRCConnection: Removed command observer " + observer);
- commandObservers.deleteObserver( observer );
- }
-
-
- public State getState()
- {
- return state;
- }
-
- public ClientState getClientState()
- {
- return clientState;
- }
-
- /**
- * Accepts a command to be sent. Sends the command to the
- * CommandSender.
- *
- * @param command Command we will send
- * */
- public void sendCommand( OutCommand command )
- {
- commandSender.sendCommand( command );
- }
-
- /**
- * @return the first class in a chain of OutCommandProcessors.
- * */
- public CommandSender getCommandSender()
- {
- return commandSender;
- }
-
- /**
- * @param sender sets the class that is responsible for sending commands.
- * */
- public void setCommandSender( CommandSender sender )
- {
- this.commandSender = sender;
- }
-
- /**
- * @return the local address to which the socket is bound.
- * */
- public InetAddress getLocalAddress()
- {
- return socket.getLocalAddress();
- }
-
- public String getRemotehost()
- {
- return clientState.getServer();
- }
-
- /**
- * Sets the time in milliseconds we wait after each command is sent.
- *
- * @param sleepTime Length of time to sleep between commands
- * */
- public void setSendDelay( int sleepTime )
- {
- this.sendDelay = sleepTime;
- }
-
- /**
- * @since 0.3.2
- * @return a class that can schedule timer tasks.
- * */
- public CronManager getCronManager()
- {
- if( cronManager == null )
- cronManager = new CronManager();
- return cronManager;
- }
-
- /**
- * Inserts into the event queue a command that was not directly
- * received from the server.
- *
- * @param fakeCommand Fake command to inject into incoming queue
- * */
- public void injectCommand( String fakeCommand )
- {
- synchronized( eventMonitor )
- {
- localEventQueue.add( fakeCommand );
- eventMonitor.notifyAll();
- }
- }
-
- // ===== package methods =============================================
-
- void socketError( IOException ioe )
- {
- //log.debug("Socket error called.");
- //log.debug("IRCConnection: The stack of the exception:", ioe);
-
- //log.log(Level.SEVERE, "Socket error", ioe);
- disconnect();
- }
-
- /**
- * Splits a raw IRC command into three parts, the prefix, identifier,
- * and parameters.
- * @param wholeString String to be parsed
- * @return a String array with 3 components, {prefix,ident,params}.
- * */
- public static String[] parseRawString( String wholeString )
- {
- String prefix = "";
- String identifier;
- String params = "";
-
- StringTokenizer tokens = new StringTokenizer( wholeString, " " );
-
- if( wholeString.charAt(0) == ':' )
- {
- prefix = tokens.nextToken();
- prefix = prefix.substring( 1, prefix.length() );
- }
-
- identifier = tokens.nextToken();
-
- if( tokens.hasMoreTokens() )
- {
- // The rest of the string
- params = tokens.nextToken("");
- }
-
- String[] result = new String[3];
- result[0] = prefix;
- result[1] = identifier;
- result[2] = params;
-
- return result;
- }
-
- /**
- * Given the three parts of an IRC command, generates an object to
- * represent that command.
- *
- * @param prefix Prefix of command object
- * @param identifier ID of command
- * @param params Params of command
- * @return An InCommand object for the given command object
- * */
- protected InCommand getCommandObject( String prefix, String identifier, String params )
- {
- InCommand command;
-
- // Remember that commands are also factories.
- InCommand commandFactory = commandRegister.getCommand( identifier );
- if( commandFactory == null )
- {
- if( UnknownError.isError( identifier ) )
- {
- command = new UnknownError( identifier );
- log.warning("IRCConnection: Using " + command);
- }
- else if( UnknownReply.isReply( identifier ) )
- {
- command = new UnknownReply( identifier );
- //log.warning("IRCConnection: Using " + command);
- }
- else
- {
- // The identifier doesn't map to a command.
- log.warning("IRCConnection: Unknown command");
- command = new UnknownCommand();
- }
- }
- else
- {
- command = commandFactory.parse( prefix, identifier, params);
-
- if( command == null )
- {
- log.severe("IRCConnection: CommandFactory[" + commandFactory + "] returned NULL");
- return null;
- }
- //log.debug("IRCConnection: Using " + command);
- }
-
- return command;
- }
-
-
-
- /**
- * Executed by the event thread.
- *
- * @param wholeString String to be parsed and handled
- * */
- void incomingCommand( String wholeString )
- {
- //log.info("IRCConnection: RCV = " + wholeString);
-
- // 1) Parse out the command
- String cmdBits[];
-
- try
- {
- cmdBits = parseRawString( wholeString );
- }
- catch( Exception e )
- {
- // So.. we can't process the command.
- // So we call the error handler.
- handleUnparsableCommand( wholeString, e );
- return;
- }
-
- String prefix = cmdBits[0];
- String identifier = cmdBits[1];
- String params = cmdBits[2];
-
- // 2) Fetch command from factory
- InCommand command = getCommandObject( prefix, identifier, params );
- command.setSourceString( wholeString );
-
- // Update the state and send out to commandObservers
- localCommandUpdate( command );
- }
-
- protected void handleUnparsableCommand( String wholeString, Exception e )
- {
- log.log(Level.SEVERE, "Unable to parse server message.", e );
- }
-
- /**
- * Called only in the event thread.
- *
- * @param command Command to update
- * */
- private void localCommandUpdate( InCommand command )
- {
- // 3) Change the connection state if required
- // This allows us to change from UNREGISTERED to REGISTERED and
- // possibly back.
- State cmdState = command.getState();
- if( cmdState != State.UNKNOWN && cmdState != getState() )
- setState( cmdState );
-
- // TODO: Bug here?
-
- // 4) Notify command observers
- try
- {
- commandObservers.setChanged();
- commandObservers.notifyObservers( command );
- }
- catch( Throwable e )
- {
- log.log(Level.SEVERE, "IRCConnection: Command notify failed.", e);
- }
-
- }
-
- // ===== private variables ===========================================
-
- /** Object used to send commands. */
- private CommandSender commandSender;
-
- private CronManager cronManager;
-
- /** State of the session. */
- private State state;
-
- /**
- * Client state (our name, nick, groups, etc). Stored here mainly
- * because there isn't anywhere else to stick it.
- */
- private ClientState clientState;
-
- /**
- * Maintains a list of classes observing the state and notifies them
- * when it changes.
- */
- private StateObserver stateObservers;
-
- /**
- * Maintains a list of classes observing commands when they come in.
- */
- private CommandObserver commandObservers;
-
- /**
- * The actual socket used for communication.
- */
- private Socket socket;
-
- /**
- * Monitor access to socket.
- * */
- private final Object socketMonitor = new Object();
-
- /**
- * We want to prevent connecting and disconnecting at the same time.
- */
- private final Object connectMonitor = new Object();
-
- /**
- * This object should be notified if we want the main thread to check for
- * events. An event is either an incoming message or a disconnect request.
- * Sending commands to the server is synchronized by the eventMonitor.
- */
- private final Object eventMonitor = new Object();
-
- /**
- * This tells the processEvents() method to check if we should disconnect
- * after processing all incoming messages.
- */
- // Protected by:
- // inputHandlerMonitor
- // eventMonitor
- // connectMonitor
- private boolean disconnectPending = false;
-
- /**
- * The writer to use for output.
- */
- private BufferedWriter socketWriter;
-
- /**
- * Command register, contains a list of commands that can be received
- * by the server and have matching Command objects.
- */
- private CommandRegister commandRegister;
-
- /**
- * Maintains a handle on the input handler.
- */
- private InputHandler inputHandler;
-
- /**
- * Access control for the input handler.
- */
- private final Object inputHandlerMonitor = new Object();
-
- /**
- * State queue keeps a queue of requests to switch state.
- */
- private LinkedList<State> stateQueue;
-
- /**
- * localEventQueue allows events not received from the server to be
- * processed.
- * */
- private LinkedList<String> localEventQueue;
-
- /**
- * Setting state prevents setState from recursing in an uncontrolled
- * manner.
- */
- private boolean settingState = false;
-
- /**
- * Event thread waits for events and executes them.
- */
- private EventThread eventThread;
-
- /**
- * Determines the time to sleep every time we send a message. We do this
- * so that the server doesn't boot us for flooding.
- */
- private int sendDelay = 300;
-
- /**
- * connected just keeps track of whether we are connected or not.
- */
- private boolean connected = false;
-
- /**
- * Are we set to be a daemon thread?
- */
- private boolean daemon = false;
-
- // ===== private methods =============================================
-
- /**
- * Unsafe, because this method can only be called by a method that has a lock
- * on connectMonitor.
- *
- * @param socket Socket to connect over
- * @param server Server to connect to
- * @throws IOException if connection fails
- */
- private void connectUnsafe( Socket socket, String server )
- throws IOException
- {
- synchronized(socketMonitor)
- {
- this.socket = socket;
- }
-
- socketWriter =
- new BufferedWriter( new OutputStreamWriter( socket.getOutputStream() ) );
-
- /**
- * The reader to use for input. Managed by the InputHandler.
- */
- BufferedReader socketReader =
- new BufferedReader( new InputStreamReader( socket.getInputStream() ) );
-
- // A simple thread that waits for input from the server and passes
- // it off to the IRCConnection class.
- //if( inputHandler != null )
- //{
- // log.fatal("IRCConnection: Non-null input handler on connect!!");
- // return;
- //}
-
- synchronized( inputHandlerMonitor )
- {
- // Pending events get processed after a disconnect call, and there
- // shouldn't be any events generated while disconnected, so it makes
- // sense to test for this condition.
- if( inputHandler != null && inputHandler.pendingMessages() )
- {
- log.severe("IRCConnection: Tried to connect, but there are pending messages!");
- return;
- }
-
- if( inputHandler != null && inputHandler.isAlive() )
- {
- log.severe("IRCConnection: Tried to connect, but the input handler is still alive!");
- return;
- }
-
- clientState.setServer( server );
- clientState.setPort( socket.getPort() );
-
- connected = true;
-
- inputHandler = new InputHandler( socketReader, this, eventMonitor );
- inputHandler.setDaemon( daemon );
- inputHandler.start();
- }
- setState( State.UNREGISTERED );
- }
-
- private class EventThread extends Thread
- {
- private boolean doShutdown = false;
-
- public EventThread()
- {
- super("EventThread");
- }
-
- public void run()
- {
- handleEvents();
- }
-
- public void shutdown()
- {
- synchronized(eventMonitor)
- {
- this.doShutdown = true;
- eventMonitor.notifyAll();
- }
- }
-
- private void handleEvents()
- {
- try
- {
- while( true )
- {
- // Process all events in the event queue.
- //log.debug("IRCConnection: Processing events");
- while( processEvents() ) { }
-
- // We can't process events while synchronized on the
- // eventMonitor because we may end up in deadlock.
- synchronized( eventMonitor )
- {
- if( !doShutdown && !pendingEvents() )
- {
- eventMonitor.wait();
- }
-
- if( doShutdown )
- {
- return;
- }
- }
- }
- }
- catch( InterruptedException ie )
- {
- log.log(Level.WARNING, "Interrupted while handling events.", ie );
- // And we do what?
- // We die, that's what we do.
- }
- }
-
- public void doStop()
- {
- shutdown();
- }
-
- public String toString()
- {
- return "EventThread";
- }
- }
-
- /**
- * This method synchronizes on the inputHandlerMonitor. Note that if
- * additional event types are processed, they also need to be added to
- * pendingEvents().
- * @return true if events were processed, false if there were no events to
- * process.
- */
- private boolean processEvents()
- {
- boolean events = false;
-
- // the inputHandlerMonitor here serves two purposes: To protect
- // from inputHandler changes and to ensure only one thread is
- // operating in processEvents.
- //
- // Perhaps a different monitor should be used?
- synchronized( inputHandlerMonitor )
- {
- while( inputHandler != null && inputHandler.pendingMessages() )
- {
- String msg = inputHandler.getMessage();
- incomingCommand( msg );
- events = true;
- }
-
- while( localEventQueue != null && !localEventQueue.isEmpty() )
- {
- String msg = localEventQueue.removeFirst();
- incomingCommand( msg );
- events = true;
- }
-
- if( disconnectPending )
- {
- //log.debug("IRCConnection: Process events: Disconnect pending.");
- doDisconnect();
- events = true;
- }
- }
-
- return events;
- }
-
- /**
- * Does no synchronization on its own. This does not synchronize on
- * any of the IRCConnection monitors or objects and returns after making a
- * minimum of method calls.
- * @return true if there are pending events that need processing.
- */
- private boolean pendingEvents()
- {
- if( inputHandler != null && inputHandler.pendingMessages() )
- return true;
- if( disconnectPending )
- return true;
- if( localEventQueue != null && !localEventQueue.isEmpty() )
- return true;
-
- return false;
- }
-
- // Synchronized by inputHandlerMonitor, called only from processEvents.
- private void doDisconnect()
- {
- synchronized( connectMonitor )
- {
- disconnectPending = false;
-
- if( !connected )
- {
- return;
- }
- connected = false;
-
- try
- {
- final long startTime = System.currentTimeMillis();
- final long sleepTime = 1000;
- final long stopTime = startTime + sleepTime;
- //log.debug("IRCConnection: Sleeping for a bit ("
- // + sleepTime + ")..");
- // Slow things down a bit so the server doesn't kill us
- // Also, we want to give a second to let any pending messages
- // get processed and any pending disconnect() calls to be made.
- // It is important that we use wait instead of sleep!
- while( stopTime - System.currentTimeMillis() > 0 )
- {
- connectMonitor.wait( stopTime - System.currentTimeMillis() );
- }
- }
- catch( InterruptedException ie )
- {
- // Ignore
- }
-
- //log.debug("IRCConnection: Stopping input handler.");
- // Deprecated?
- // inputHandler.stop();
- // inputHandler = null;
-
- //log.debug("IRCConnection: Closing socket.");
- try
- {
- socket.close();
- }
- catch( IOException ioe )
- {
- // And we are supposed to do what?
- // This probably means we've called disconnect on a closed
- // socket.
- handleSocketCloseException( ioe );
- return;
- }
- finally
- {
- connected = false;
- }
- }
-
- // The input handler should die, because we closed the socket.
- // We'll wait for it to die.
- synchronized( inputHandlerMonitor )
- {
- //log.debug("IRCConnection: Waiting for the input handler to die..");
- try
- {
- // log.debug("IRCConnection: Stack:");
-
- if( inputHandler.isAlive() )
- inputHandler.join();
- else
- {
- //log.debug("IRCConnection: No waiting required, input hander is already dead.");
- }
- }
- catch( InterruptedException ie )
- {
- //log.debug("IRCConnection: Error in join(): " + ie);
- }
- //log.debug("IRCConnection: Done waiting for the input handler to die.");
- }
-
-
- // There may be pending messages that we should process before we
- // actually notify all the state listeners.
- processEvents();
-
- // It is important that the state be switched last. One of the
- // state listeners may try to re-connect us.
- setState( State.UNCONNECTED );
-
- }
-
- protected void handleSocketCloseException( IOException ioe )
- {
- log.log(Level.WARNING, "Error closing socket.", ioe );
- }
-
- /**
- * Signals to trigger a state change. Won't actually send a state change
- * until a previous attempt at changing the state finishes. This is
- * important if one of the state listeners affects the state (ie tries to
- * reconnect if we disconnect, etc).
- *
- * @param newState New state to set connection to.
- */
- private void setState( State newState )
- {
- if( settingState )
- {
- // We are already setting the state. We want to complete changing
- // to one state before changing to another, so that we don't have
- // out-of-order state change signals.
- stateQueue.addLast( newState );
- return;
- }
-
- settingState = true;
-
- if( state == newState )
- return;
-
- while( true )
- {
- state = newState;
-
- //log.debug("IRCConnection: State switch: " + state);
-
- try
- {
- stateObservers.setChanged();
- stateObservers.notifyObservers( newState );
- }
- catch( Throwable e )
- {
- log.log(Level.SEVERE, "IRCConnection: State update failed.", e);
- }
-
- if( stateQueue.isEmpty() )
- break;
- newState = stateQueue.removeFirst();
- }
-
- settingState = false;
- }
-
- private class DefaultCommandSender implements CommandSender
- {
- public CommandSender getNextCommandSender()
- {
- return null;
- }
-
- public void sendCommand( OutCommand oc )
- {
- finalSendCommand( oc.render() );
- }
- }
-
- /**
- * Sends the command down the socket, with the required 'CRLF' on the
- * end. Waits for a little bit after sending the command so that we don't
- * accidentally flood the server.
- *
- * @param str String to send
- */
- private void finalSendCommand( String str )
- {
- try
- {
- synchronized( eventMonitor )
- {
- //log.info("IRCConnection: SEND= " + str);
-
- if( disconnectPending )
- {
- //log.debug("IRCConnection: Send cancelled, disconnect pending.");
- return;
- }
-
- socketWriter.write( str + "\r\n" );
- socketWriter.flush();
-
- try
- {
- // Slow things down a bit so the server doesn't kill us
- // We do this after the send so that if the send fails the
- // exception is handled right away.
- Thread.sleep( sendDelay );
- }
- catch( InterruptedException ie )
- {
- // Ignore
- }
- }
- }
- catch( IOException ioe )
- {
- socketError( ioe );
- }
- }
- // ----- END IRCConnection -------------------------------------------
-}