File Source: IRCParser.java

         /* 
    P/P   *  Method: com.dmdirc.parser.irc.IRCParser__static_init
          */
     1  /*
     2   * Copyright (c) 2006-2009 Chris Smith, Shane Mc Cormack, Gregory Holmes
     3   *
     4   * Permission is hereby granted, free of charge, to any person obtaining a copy
     5   * of this software and associated documentation files (the "Software"), to deal
     6   * in the Software without restriction, including without limitation the rights
     7   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     8   * copies of the Software, and to permit persons to whom the Software is
     9   * furnished to do so, subject to the following conditions:
    10   *
    11   * The above copyright notice and this permission notice shall be included in
    12   * all copies or substantial portions of the Software.
    13   *
    14   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    15   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    16   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    17   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    18   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    19   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    20   * SOFTWARE.
    21   */
    22  
    23  package com.dmdirc.parser.irc;
    24  
    25  import com.dmdirc.parser.irc.callbacks.CallbackManager;
    26  
    27  import java.io.BufferedReader;
    28  import java.io.IOException;
    29  import java.io.InputStreamReader;
    30  import java.io.PrintWriter;
    31  import java.net.InetAddress;
    32  import java.net.InetSocketAddress;
    33  import java.net.Proxy;
    34  import java.net.Socket;
    35  import java.net.UnknownHostException;
    36  import java.security.KeyManagementException;
    37  import java.security.NoSuchAlgorithmException;
    38  import java.security.cert.X509Certificate;
    39  import java.util.Arrays;
    40  import java.util.Collection;
    41  import java.util.Hashtable;
    42  import java.util.LinkedList;
    43  import java.util.Map;
    44  import java.util.Timer;
    45  import java.util.Queue;
    46  import java.util.concurrent.Semaphore;
    47  import java.util.concurrent.atomic.AtomicBoolean;
    48  
    49  import javax.net.ssl.KeyManager;
    50  import javax.net.ssl.SSLContext;
    51  import javax.net.ssl.SSLSocketFactory;
    52  import javax.net.ssl.TrustManager;
    53  import javax.net.ssl.X509TrustManager;
    54  
    55  /**
    56   * IRC Parser.
    57   *
    58   * @author Shane Mc Cormack
    59   */
    60  public class IRCParser implements Runnable {
    61  
    62  	/** Max length an outgoing line should be (NOT including \r\n). */
    63  	public static final int MAX_LINELENGTH = 510;
    64  
    65  	/** General Debug Information. */
    66  	public static final int DEBUG_INFO = 1;
    67  	/** Socket Debug Information. */
    68  	public static final int DEBUG_SOCKET = 2;
    69  	/** Processing Manager Debug Information. */
    70  	public static final int DEBUG_PROCESSOR = 4;
    71  	/** List Mode Queue Debug Information. */
    72  	public static final int DEBUG_LMQ = 8;
    73  //	public static final int DEBUG_SOMETHING = 16; //Next thingy
    74  
    75  	/** Attempt to update user host all the time, not just on Who/Add/NickChange. */
    76  	static final boolean ALWAYS_UPDATECLIENT = true;
    77  
    78  	/** Byte used to show that a non-boolean mode is a list (b). */
    79  	static final byte MODE_LIST = 1;
    80  	/** Byte used to show that a non-boolean mode is not a list, and requires a parameter to set (lk). */
    81  	static final byte MODE_SET = 2;
    82  	/** Byte used to show that a non-boolean mode is not a list, and requires a parameter to unset (k). */
    83  	static final byte MODE_UNSET = 4;
    84  
    85  	/**
    86  	 * This is what the user wants settings to be.
    87  	 * Nickname here is *not* always accurate.<br><br>
    88  	 * ClientInfo variable tParser.getMyself() should be used for accurate info.
    89  	 */
    90  	public MyInfo me = new MyInfo();
    91  	/**	Server Info requested by user. */
    92  	public ServerInfo server = new ServerInfo();
    93  
    94  	/** Timer for server ping. */
    95  	private Timer pingTimer = null;
    96      /** Semaphore for access to pingTimer. */
    97      private Semaphore pingTimerSem = new Semaphore(1);
    98  	/** Length of time to wait between ping stuff. */
    99  	private long pingTimerLength = 10000;
   100  	/** Is a ping needed? */
   101  	private volatile AtomicBoolean pingNeeded = new AtomicBoolean(false);
   102  	/** Time last ping was sent at. */
   103  	private long pingTime;
   104  	/** Current Server Lag. */
   105  	private long serverLag;
   106  	/** Last value sent as a ping argument. */
   107  	private String lastPingValue = "";
   108  
   109  	/**
   110  	 * Count down to next ping.
   111  	 * The timer fires every 10 seconds, this value is decreased every time the
   112  	 * timer fires.<br>
   113  	 * Once it reaches 0, we send a ping, and reset it to 6, this means we ping
   114  	 * the server every minute.
   115  	 *
   116  	 * @see setPingCountDownLength
   117  	 */
   118  	private int pingCountDown;
   119  	/**
   120  	 * Amount of times the timer has to fire for inactivity before sending a ping.
   121  	 *
   122  	 * @see setPingCountDownLength
   123  	 */
   124  	private int pingCountDownLength = 6;
   125  
   126  	/** Name the server calls itself. */
   127  	String sServerName;
   128  
   129  	/** Network name. This is "" if no network name is provided */
   130  	String sNetworkName;
   131  
   132  	/** This is what we think the nickname should be. */
   133  	String sThinkNickname;
   134  
   135  	/** When using inbuilt pre-001 NickInUse handler, have we tried our AltNick. */
   136  	boolean triedAlt;
   137  
   138  	/** Have we recieved the 001. */
   139  	boolean got001;
   140  	/** Have we fired post005? */
   141  	boolean post005;
   142  	/** Has the thread started execution yet, (Prevents run() being called multiple times). */
   143  	boolean hasBegan;
   144  
   145  	/** Hashtable storing known prefix modes (ohv). */
   146  	final Map<Character, Long> hPrefixModes = new Hashtable<Character, Long>();
   147  	/**
   148  	 * Hashtable maping known prefix modes (ohv) to prefixes (@%+) - Both ways.
   149  	 * Prefix map contains 2 pairs for each mode. (eg @ => o and o => @)
   150  	 */
   151  	final Map<Character, Character> hPrefixMap = new Hashtable<Character, Character>();
   152  	/** Integer representing the next avaliable integer value of a prefix mode. */
   153  	long nNextKeyPrefix = 1;
   154  	/** Hashtable storing known user modes (owxis etc). */
   155  	final Map<Character, Long> hUserModes = new Hashtable<Character, Long>();
   156  	/** Integer representing the next avaliable integer value of a User mode. */
   157  	long nNextKeyUser = 1;
   158  	/**
   159  	 * Hashtable storing known boolean chan modes (cntmi etc).
   160  	 * Valid Boolean Modes are stored as Hashtable.pub('m',1); where 'm' is the mode and 1 is a numeric value.<br><br>
   161  	 * Numeric values are powers of 2. This allows up to 32 modes at present (expandable to 64)<br><br>
   162  	 * ChannelInfo/ChannelClientInfo etc provide methods to view the modes in a human way.<br><br>
   163  	 * <br>
   164  	 * Channel modes discovered but not listed in 005 are stored as boolean modes automatically (and a ERROR_WARNING Error is called)
   165  	 */
   166  	final Map<Character, Long> hChanModesBool = new Hashtable<Character, Long>();
   167  	/** Integer representing the next avaliable integer value of a Boolean mode. */
   168  
   169  	long nNextKeyCMBool = 1;
   170  
   171  	/**
   172  	 * Hashtable storing known non-boolean chan modes (klbeI etc).
   173  	 * Non Boolean Modes (for Channels) are stored together in this hashtable, the value param
   174  	 * is used to show the type of variable. (List (1), Param just for set (2), Param for Set and Unset (2+4=6))<br><br>
   175  	 * <br>
   176  	 * see MODE_LIST<br>
   177  	 * see MODE_SET<br>
   178  	 * see MODE_UNSET<br>
   179  	 */
   180  	final Map<Character, Byte> hChanModesOther = new Hashtable<Character, Byte>();
   181  
   182  	/** The last line of input recieved from the server */
   183  	String lastLine = "";
   184  	/** Should the lastline (where given) be appended to the "data" part of any onErrorInfo call? */
   185  	boolean addLastLine = false;
   186  
   187  	/**
   188  	* Channel Prefixes (ie # + etc).
   189  	* The "value" for these is always true.
   190  	*/
   191  	final Map<Character, Boolean> hChanPrefix = new Hashtable<Character, Boolean>();
   192  	/** Hashtable storing all known clients based on nickname (in lowercase). */
   193  	private final Map<String, ClientInfo> hClientList = new Hashtable<String, ClientInfo>();
   194  	/** Hashtable storing all known channels based on chanel name (inc prefix - in lowercase). */
   195  	private final Map<String, ChannelInfo> hChannelList = new Hashtable<String, ChannelInfo>();
   196  	/** Reference to the ClientInfo object that references ourself. */
   197  	private ClientInfo cMyself = new ClientInfo(this, "myself").setFake(true);
   198  	/** Hashtable storing all information gathered from 005. */
   199  	final Map<String, String> h005Info = new Hashtable<String, String>();
   200  
   201  	/** Ignore List. */
   202  	RegexStringList myIgnoreList = new RegexStringList();
   203  
   204  	/** Reference to the callback Manager. */
   205  	CallbackManager myCallbackManager = new CallbackManager(this);
   206  	/** Reference to the Processing Manager. */
   207  	ProcessingManager myProcessingManager = new ProcessingManager(this);
   208  
   209  	/** Should we automatically disconnect on fatal errors?. */
   210  	private boolean disconnectOnFatal = true;
   211  
   212  	/** Current Socket State. */
   213  	protected SocketState currentSocketState = SocketState.NULL;
   214  
   215  	/** This is the socket used for reading from/writing to the IRC server. */
   216  	private Socket socket;
   217  	/** Used for writing to the server. */
   218  	private PrintWriter out;
   219  	/** Used for reading from the server. */
   220  	private BufferedReader in;
   221  
   222  	/** This is the default TrustManager for SSL Sockets, it trusts all ssl certs. */
   223  	private final TrustManager[] trustAllCerts = {
        		 /* 
    P/P 		  *  Method: void com.dmdirc.parser.irc.IRCParser$1(IRCParser)
        		  */
   224  		new X509TrustManager() {
   225  			@Override
        			 /* 
    P/P 			  *  Method: X509Certificate[] getAcceptedIssuers()
        			  * 
        			  *  Postconditions:
        			  *    return_value == null
        			  */
   226  			public X509Certificate[] getAcceptedIssuers() { return null; }
   227  			@Override
        			 /* 
    P/P 			  *  Method: void checkClientTrusted(X509Certificate[], String)
        			  */
   228  			public void checkClientTrusted(final X509Certificate[] certs, final String authType) { }
   229  			@Override
        			 /* 
    P/P 			  *  Method: void checkServerTrusted(X509Certificate[], String)
        			  */
   230  			public void checkServerTrusted(final X509Certificate[] certs, final String authType) { }
   231  		},
   232  	};
   233  
   234  	/** Should fake (channel)clients be created for callbacks where they do not exist? */
   235  	boolean createFake = false;
   236  
   237  	/** Should channels automatically request list modes? */
   238  	boolean autoListMode = true;
   239  
   240  	/** Should part/quit/kick callbacks be fired before removing the user internally? */
   241  	boolean removeAfterCallback = true;
   242  
   243  	/** This is the TrustManager used for SSL Sockets. */
   244  	private TrustManager[] myTrustManager = trustAllCerts;
   245  
   246  	/** The KeyManagers used for client certificates for SSL sockets. */
   247  	private KeyManager[] myKeyManagers;
   248  
   249  	/** This is the IP we want to bind to. */
   250  	private String bindIP = "";
   251  
   252  	/**
   253  	 * Default constructor, ServerInfo and MyInfo need to be added separately (using IRC.me and IRC.server).
   254  	 */
        	 /* 
    P/P 	  *  Method: void com.dmdirc.parser.irc.IRCParser()
        	  */
   255  	public IRCParser() { this(null, null); }
   256  	/**
   257  	 * Constructor with ServerInfo, MyInfo needs to be added separately (using IRC.me).
   258  	 *
   259  	 * @param serverDetails Server information.
   260  	 */
        	 /* 
    P/P 	  *  Method: void com.dmdirc.parser.irc.IRCParser(ServerInfo)
        	  */
   261  	public IRCParser(final ServerInfo serverDetails) { this(null, serverDetails); }
   262  	/**
   263  	 * Constructor with MyInfo, ServerInfo needs to be added separately (using IRC.server).
   264  	 *
   265  	 * @param myDetails Client information.
   266  	 */
        	 /* 
    P/P 	  *  Method: void com.dmdirc.parser.irc.IRCParser(MyInfo)
        	  */
   267  	public IRCParser(final MyInfo myDetails) { this(myDetails, null); }
   268  	/**
   269  	 * Constructor with ServerInfo and MyInfo.
   270  	 *
   271  	 * @param serverDetails Server information.
   272  	 * @param myDetails Client information.
   273  	 */
        	 /* 
    P/P 	  *  Method: void com.dmdirc.parser.irc.IRCParser(MyInfo, ServerInfo)
        	  * 
        	  *  Preconditions:
        	  *    (soft) com.dmdirc.parser.irc.callbacks.CallbackManager__static_init.new Class[](CallbackManager__static_init#1)[...] != null
        	  * 
        	  *  Postconditions:
        	  *    java.lang.StringBuilder:toString(...)._tainted == 0
        	  *    init'ed(this.addLastLine)
        	  *    this.autoListMode == 1
        	  *    this.disconnectOnFatal == 1
        	  *    this.nNextKeyCMBool == 1
        	  *    this.nNextKeyPrefix == 1
        	  *    this.nNextKeyUser == 1
        	  *    this.removeAfterCallback == 1
        	  *    new ArrayList(RegexStringList#1) num objects == 1
        	  *    new AtomicBoolean(IRCParser#4) num objects == 1
        	  *    ...
        	  * 
        	  *  Test Vectors:
        	  *    myDetails: Addr_Set{null}, Inverse{null}
        	  *    serverDetails: Addr_Set{null}, Inverse{null}
        	  */
   274  	public IRCParser(final MyInfo myDetails, final ServerInfo serverDetails) {
   275  		if (myDetails != null) { this.me = myDetails; }
   276  		if (serverDetails != null) { this.server = serverDetails; }
   277  		resetState();
   278  	}
   279  
   280  	/**
   281  	 * Get the current Value of bindIP.
   282  	 *
   283  	 * @return Value of bindIP ("" for default IP)
   284  	 */
        	 /* 
    P/P 	  *  Method: String getBindIP()
        	  * 
        	  *  Preconditions:
        	  *    init'ed(this.bindIP)
        	  * 
        	  *  Postconditions:
        	  *    return_value == this.bindIP
        	  *    init'ed(return_value)
        	  */
   285  	public String getBindIP() { return bindIP; }
   286  
   287  	/**
   288  	 * Set the current Value of bindIP.
   289  	 *
   290  	 * @param newValue New value to set bindIP
   291  	 */
        	 /* 
    P/P 	  *  Method: void setBindIP(String)
        	  * 
        	  *  Postconditions:
        	  *    this.bindIP == newValue
        	  *    init'ed(this.bindIP)
        	  */
   292  	public void setBindIP(final String newValue) { bindIP = newValue; }
   293  
   294  	/**
   295  	 * Get the current Value of createFake.
   296  	 *
   297  	 * @return Value of createFake (true if fake clients will be added for callbacks, else false)
   298  	 */
        	 /* 
    P/P 	  *  Method: bool getCreateFake()
        	  * 
        	  *  Preconditions:
        	  *    init'ed(this.createFake)
        	  * 
        	  *  Postconditions:
        	  *    return_value == this.createFake
        	  *    init'ed(return_value)
        	  */
   299  	public boolean getCreateFake() { return createFake; }
   300  
   301  	/**
   302  	 * Set the current Value of createFake.
   303  	 *
   304  	 * @param newValue New value to set createFake
   305  	 */
        	 /* 
    P/P 	  *  Method: void setCreateFake(bool)
        	  * 
        	  *  Postconditions:
        	  *    this.createFake == newValue
        	  *    init'ed(this.createFake)
        	  */
   306  	public void setCreateFake(final boolean newValue) { createFake = newValue; }
   307  
   308  	/**
   309  	 * Get the current Value of autoListMode.
   310  	 *
   311  	 * @return Value of autoListMode (true if channels automatically ask for list modes on join, else false)
   312  	 */
        	 /* 
    P/P 	  *  Method: bool getAutoListMode()
        	  * 
        	  *  Preconditions:
        	  *    init'ed(this.autoListMode)
        	  * 
        	  *  Postconditions:
        	  *    return_value == this.autoListMode
        	  *    init'ed(return_value)
        	  */
   313  	public boolean getAutoListMode() { return autoListMode; }
   314  
   315  	/**
   316  	 * Set the current Value of autoListMode.
   317  	 *
   318  	 * @param newValue New value to set autoListMode
   319  	 */
        	 /* 
    P/P 	  *  Method: void setAutoListMode(bool)
        	  * 
        	  *  Postconditions:
        	  *    this.autoListMode == newValue
        	  *    init'ed(this.autoListMode)
        	  */
   320  	public void setAutoListMode(final boolean newValue) { autoListMode = newValue; }
   321  
   322  	/**
   323  	 * Get the current Value of removeAfterCallback.
   324  	 *
   325  	 * @return Value of removeAfterCallback (true if kick/part/quit callbacks are fired before internal removal)
   326  	 */
        	 /* 
    P/P 	  *  Method: bool getRemoveAfterCallback()
        	  * 
        	  *  Preconditions:
        	  *    init'ed(this.removeAfterCallback)
        	  * 
        	  *  Postconditions:
        	  *    return_value == this.removeAfterCallback
        	  *    init'ed(return_value)
        	  */
   327  	public boolean getRemoveAfterCallback() { return removeAfterCallback; }
   328  
   329  	/**
   330  	 * Get the current Value of removeAfterCallback.
   331  	 *
   332  	 * @param newValue New value to set removeAfterCallback
   333  	 */
        	 /* 
    P/P 	  *  Method: void setRemoveAfterCallback(bool)
        	  * 
        	  *  Postconditions:
        	  *    this.removeAfterCallback == newValue
        	  *    init'ed(this.removeAfterCallback)
        	  */
   334  	public void setRemoveAfterCallback(final boolean newValue) { removeAfterCallback = newValue; }
   335  
   336  	/**
   337  	 * Get the current Value of addLastLine.
   338  	 *
   339  	 * @return Value of addLastLine (true if lastLine info will be automatically
   340  	 *         added to the errorInfo data line). This should be true if lastLine
   341  	 *         isn't handled any other way.
   342  	 */
        	 /* 
    P/P 	  *  Method: bool getAddLastLine()
        	  * 
        	  *  Preconditions:
        	  *    init'ed(this.addLastLine)
        	  * 
        	  *  Postconditions:
        	  *    return_value == this.addLastLine
        	  *    init'ed(return_value)
        	  */
   343  	public boolean getAddLastLine() { return addLastLine; }
   344  
   345  	/**
   346  	 * Get the current Value of addLastLine.
   347  	 * This returns "this" and thus can be used in the construction line.
   348  	 *
   349  	 * @param newValue New value to set addLastLine
   350  	 */
        	 /* 
    P/P 	  *  Method: void setAddLastLine(bool)
        	  * 
        	  *  Postconditions:
        	  *    this.addLastLine == newValue
        	  *    init'ed(this.addLastLine)
        	  */
   351  	public void setAddLastLine(final boolean newValue) { addLastLine = newValue; }
   352  
   353  
   354  	/**
   355  	 * Get the current socket State.
   356  	 *
   357       * @since 0.6.3m1
   358  	 * @return Current {@link SocketState}
   359  	 */
        	 /* 
    P/P 	  *  Method: SocketState getSocketState()
        	  * 
        	  *  Preconditions:
        	  *    init'ed(this.currentSocketState)
        	  * 
        	  *  Postconditions:
        	  *    return_value == this.currentSocketState
        	  *    init'ed(return_value)
        	  */
   360  	public SocketState getSocketState() { return currentSocketState; }
   361  
   362  	/**
   363  	 * Get a reference to the Processing Manager.
   364  	 *
   365  	 * @return Reference to the CallbackManager
   366  	 */
        	 /* 
    P/P 	  *  Method: ProcessingManager getProcessingManager()
        	  * 
        	  *  Preconditions:
        	  *    init'ed(this.myProcessingManager)
        	  * 
        	  *  Postconditions:
        	  *    return_value == this.myProcessingManager
        	  *    init'ed(return_value)
        	  */
   367  	public ProcessingManager getProcessingManager() { return myProcessingManager;	}
   368  
   369  	/**
   370  	 * Get a reference to the CallbackManager.
   371  	 *
   372  	 * @return Reference to the CallbackManager
   373  	 */
        	 /* 
    P/P 	  *  Method: CallbackManager getCallbackManager()
        	  * 
        	  *  Preconditions:
        	  *    init'ed(this.myCallbackManager)
        	  * 
        	  *  Postconditions:
        	  *    return_value == this.myCallbackManager
        	  *    init'ed(return_value)
        	  */
   374  	public CallbackManager getCallbackManager() { return myCallbackManager;	}
   375  
   376  	/**
   377  	 * Get a reference to the default TrustManager for SSL Sockets.
   378  	 *
   379  	 * @return a reference to trustAllCerts
   380  	 */
        	 /* 
    P/P 	  *  Method: TrustManager[] getDefaultTrustManager()
        	  * 
        	  *  Postconditions:
        	  *    return_value == this.trustAllCerts
        	  *    init'ed(return_value)
        	  */
   381  	public TrustManager[] getDefaultTrustManager() { return trustAllCerts; }
   382  
   383  	/**
   384  	 * Get a reference to the current TrustManager for SSL Sockets.
   385  	 *
   386  	 * @return a reference to myTrustManager;
   387  	 */
        	 /* 
    P/P 	  *  Method: TrustManager[] getTrustManager()
        	  * 
        	  *  Preconditions:
        	  *    init'ed(this.myTrustManager)
        	  * 
        	  *  Postconditions:
        	  *    return_value == this.myTrustManager
        	  *    init'ed(return_value)
        	  */
   388  	public TrustManager[] getTrustManager() { return myTrustManager; }
   389  
   390  	/**
   391  	 * Replace the current TrustManager for SSL Sockets with a new one.
   392  	 *
   393  	 * @param newTrustManager Replacement TrustManager for SSL Sockets.
   394  	 */
        	 /* 
    P/P 	  *  Method: void setTrustManager(TrustManager[])
        	  * 
        	  *  Postconditions:
        	  *    this.myTrustManager == newTrustManager
        	  *    init'ed(this.myTrustManager)
        	  */
   395  	public void setTrustManager(final TrustManager[] newTrustManager) { myTrustManager = newTrustManager; }
   396  
   397  	/**
   398  	 * Replace the current KeyManagers for SSL Sockets with a new set.
   399  	 *
   400  	 * @param newKeyManagers Replacement KeyManagers for SSL Sockets.
   401  	 */
        	 /* 
    P/P 	  *  Method: void setKeyManagers(KeyManager[])
        	  * 
        	  *  Postconditions:
        	  *    this.myKeyManagers == newKeyManagers
        	  *    init'ed(this.myKeyManagers)
        	  */
   402  	public void setKeyManagers(final KeyManager[] newKeyManagers) { myKeyManagers = newKeyManagers; }
   403  
   404  	/**
   405  	 * Get a reference to the ignorelist.
   406  	 *
   407  	 * @return a reference to the ignorelist
   408  	 */
        	 /* 
    P/P 	  *  Method: RegexStringList getIgnoreList()
        	  * 
        	  *  Preconditions:
        	  *    init'ed(this.myIgnoreList)
        	  * 
        	  *  Postconditions:
        	  *    return_value == this.myIgnoreList
        	  *    init'ed(return_value)
        	  */
   409  	public RegexStringList getIgnoreList() { return myIgnoreList; }
   410  
   411  	/**
   412  	 * Replaces the current ignorelist with a new one.
   413  	 *
   414  	 * @param ignoreList Replacement ignorelist
   415  	 */
        	 /* 
    P/P 	  *  Method: void setIgnoreList(RegexStringList)
        	  * 
        	  *  Postconditions:
        	  *    this.myIgnoreList == ignoreList
        	  *    init'ed(this.myIgnoreList)
        	  */
   416  	public void setIgnoreList(final RegexStringList ignoreList) { myIgnoreList = ignoreList; }
   417  
   418  	//---------------------------------------------------------------------------
   419  	// Start Callbacks
   420  	//---------------------------------------------------------------------------
   421  
   422  	/**
   423  	 * Callback to all objects implementing the ServerError Callback.
   424  	 *
   425  	 * @see com.dmdirc.parser.irc.callbacks.interfaces.IServerError
   426  	 * @param message The error message
   427  	 * @return true if a method was called, false otherwise
   428  	 */
   429  	protected boolean callServerError(final String message) {
        		 /* 
    P/P 		  *  Method: bool callServerError(String)
        		  * 
        		  *  Preconditions:
        		  *    this.myCallbackManager != null
        		  *    this.myCallbackManager.callbackHash != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  */
   430  		return myCallbackManager.getCallbackType("OnServerError").call(message);
   431  	}
   432  
   433  	/**
   434  	 * Callback to all objects implementing the DataIn Callback.
   435  	 *
   436  	 * @see com.dmdirc.parser.irc.callbacks.interfaces.IDataIn
   437  	 * @param data Incomming Line.
   438  	 * @return true if a method was called, false otherwise
   439  	 */
   440  	protected boolean callDataIn(final String data) {
        		 /* 
    P/P 		  *  Method: bool callDataIn(String)
        		  * 
        		  *  Preconditions:
        		  *    this.myCallbackManager != null
        		  *    this.myCallbackManager.callbackHash != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  */
   441  		return myCallbackManager.getCallbackType("OnDataIn").call(data);
   442  	}
   443  
   444  	/**
   445  	 * Callback to all objects implementing the DataOut Callback.
   446  	 *
   447  	 * @param data Outgoing Data
   448  	 * @param fromParser True if parser sent the data, false if sent using .sendLine
   449  	 * @return true if a method was called, false otherwise
   450  	 * @see com.dmdirc.parser.irc.callbacks.interfaces.IDataOut
   451  	 */
   452  	protected boolean callDataOut(final String data, final boolean fromParser) {
        		 /* 
    P/P 		  *  Method: bool callDataOut(String, bool)
        		  * 
        		  *  Preconditions:
        		  *    this.myCallbackManager != null
        		  *    this.myCallbackManager.callbackHash != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  */
   453  		return myCallbackManager.getCallbackType("OnDataOut").call(data, fromParser);
   454  	}
   455  
   456  	/**
   457  	 * Callback to all objects implementing the DebugInfo Callback.
   458  	 *
   459  	 * @see com.dmdirc.parser.irc.callbacks.interfaces.IDebugInfo
   460  	 * @param level Debugging Level (DEBUG_INFO, DEBUG_SOCKET etc)
   461  	 * @param data Debugging Information as a format string
   462  	 * @param args Formatting String Options
   463  	 * @return true if a method was called, false otherwise
   464  	 */
   465  	protected boolean callDebugInfo(final int level, final String data, final Object... args) {
        		 /* 
    P/P 		  *  Method: bool callDebugInfo(int, String, Object[])
        		  * 
        		  *  Preconditions:
        		  *    this.myCallbackManager != null
        		  *    this.myCallbackManager.callbackHash != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  */
   466  		return callDebugInfo(level, String.format(data, args));
   467  	}
   468  	/**
   469  	 * Callback to all objects implementing the DebugInfo Callback.
   470  	 *
   471  	 * @see com.dmdirc.parser.irc.callbacks.interfaces.IDebugInfo
   472  	 * @param level Debugging Level (DEBUG_INFO, DEBUG_SOCKET etc)
   473  	 * @param data Debugging Information
   474  	 * @return true if a method was called, false otherwise
   475  	 */
   476  	protected boolean callDebugInfo(final int level, final String data) {
        		 /* 
    P/P 		  *  Method: bool callDebugInfo(int, String)
        		  * 
        		  *  Preconditions:
        		  *    this.myCallbackManager != null
        		  *    this.myCallbackManager.callbackHash != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  */
   477  		return myCallbackManager.getCallbackType("OnDebugInfo").call(level, data);
   478  	}
   479  
   480  	/**
   481  	 * Callback to all objects implementing the IErrorInfo Interface.
   482  	 *
   483  	 * @see com.dmdirc.parser.irc.callbacks.interfaces.IErrorInfo
   484  	 * @param errorInfo ParserError object representing the error.
   485  	 * @return true if a method was called, false otherwise
   486  	 */
   487  	protected boolean callErrorInfo(final ParserError errorInfo) {
        		 /* 
    P/P 		  *  Method: bool callErrorInfo(ParserError)
        		  * 
        		  *  Preconditions:
        		  *    this.myCallbackManager != null
        		  *    this.myCallbackManager.callbackHash != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  */
   488  		return myCallbackManager.getCallbackType("OnErrorInfo").call(errorInfo);
   489  	}
   490  
   491  	/**
   492  	 * Callback to all objects implementing the IConnectError Interface.
   493  	 *
   494  	 * @see com.dmdirc.parser.irc.callbacks.interfaces.IConnectError
   495  	 * @param errorInfo ParserError object representing the error.
   496  	 * @return true if a method was called, false otherwise
   497  	 */
   498  	protected boolean callConnectError(final ParserError errorInfo) {
        		 /* 
    P/P 		  *  Method: bool callConnectError(ParserError)
        		  * 
        		  *  Preconditions:
        		  *    this.myCallbackManager != null
        		  *    this.myCallbackManager.callbackHash != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  */
   499  		return myCallbackManager.getCallbackType("OnConnectError").call(errorInfo);
   500  	}
   501  
   502  	/**
   503  	 * Callback to all objects implementing the SocketClosed Callback.
   504  	 *
   505  	 * @see com.dmdirc.parser.irc.callbacks.interfaces.ISocketClosed
   506  	 * @return true if a method was called, false otherwise
   507  	 */
   508  	protected boolean callSocketClosed() {
        		 /* 
    P/P 		  *  Method: bool callSocketClosed()
        		  * 
        		  *  Preconditions:
        		  *    this.myCallbackManager != null
        		  *    this.myCallbackManager.callbackHash != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  */
   509  		return myCallbackManager.getCallbackType("OnSocketClosed").call();
   510  	}
   511  
   512  	/**
   513  	 * Callback to all objects implementing the PingFailed Callback.
   514  	 *
   515  	 * @see com.dmdirc.parser.irc.callbacks.interfaces.IPingFailed
   516  	 * @return true if a method was called, false otherwise
   517  	 */
   518  	protected boolean callPingFailed() {
        		 /* 
    P/P 		  *  Method: bool callPingFailed()
        		  * 
        		  *  Preconditions:
        		  *    this.myCallbackManager != null
        		  *    this.myCallbackManager.callbackHash != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  */
   519  		return myCallbackManager.getCallbackType("OnPingFailed").call();
   520  	}
   521  
   522  	/**
   523  	 * Callback to all objects implementing the PingSent Callback.
   524  	 *
   525  	 * @see com.dmdirc.parser.irc.callbacks.interfaces.IPingSent
   526  	 * @return true if a method was called, false otherwise
   527  	 */
   528  	protected boolean callPingSent() {
        		 /* 
    P/P 		  *  Method: bool callPingSent()
        		  * 
        		  *  Preconditions:
        		  *    this.myCallbackManager != null
        		  *    this.myCallbackManager.callbackHash != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  */
   529  		return myCallbackManager.getCallbackType("OnPingSent").call();
   530  	}
   531  
   532  	/**
   533  	 * Callback to all objects implementing the PingSuccess Callback.
   534  	 *
   535  	 * @see com.dmdirc.parser.irc.callbacks.interfaces.IPingSuccess
   536  	 * @return true if a method was called, false otherwise
   537  	 */
   538  	protected boolean callPingSuccess() {
        		 /* 
    P/P 		  *  Method: bool callPingSuccess()
        		  * 
        		  *  Preconditions:
        		  *    this.myCallbackManager != null
        		  *    this.myCallbackManager.callbackHash != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  */
   539  		return myCallbackManager.getCallbackType("OnPingSuccess").call();
   540  	}
   541  
   542  	/**
   543  	 * Callback to all objects implementing the Post005 Callback.
   544  	 *
   545  	 * @return true if any callbacks were called.
   546  	 * @see IPost005
   547  	 */
   548  	protected synchronized boolean callPost005() {
        		 /* 
    P/P 		  *  Method: bool callPost005()
        		  * 
        		  *  Preconditions:
        		  *    init'ed(this.post005)
        		  *    (soft) this.myCallbackManager != null
        		  *    (soft) this.myCallbackManager.callbackHash != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  *    this.post005 == 1
        		  * 
        		  *  Test Vectors:
        		  *    this.post005: {0}, {1}
        		  */
   549  		if (post005) { return false; }
   550  		post005 = true;
   551  
   552  		return getCallbackManager().getCallbackType("OnPost005").call();
   553  	}
   554  
   555  	//---------------------------------------------------------------------------
   556  	// End Callbacks
   557  	//---------------------------------------------------------------------------
   558  
   559  	/** Reset internal state (use before connect). */
   560  	private void resetState() {
   561  		// Reset General State info
        		 /* 
    P/P 		  *  Method: void resetState()
        		  * 
        		  *  Preconditions:
        		  *    init'ed(this.pingTimer)
        		  *    this.h005Info != null
        		  *    this.hChanModesBool != null
        		  *    this.hChanModesOther != null
        		  *    this.hChanPrefix != null
        		  *    this.hChannelList != null
        		  *    this.hClientList != null
        		  *    this.hPrefixMap != null
        		  *    this.hPrefixModes != null
        		  *    this.hUserModes != null
        		  *    ...
        		  * 
        		  *  Postconditions:
        		  *    this.cMyself == &amp;new ClientInfo(resetState#1)
        		  *    this.currentSocketState == &amp;com.dmdirc.parser.irc.SocketState__static_init.new SocketState(SocketState__static_init#2)
        		  *    this.got001 == 0
        		  *    this.post005 == 0
        		  *    this.triedAlt == 0
        		  *    this.lastLine == &amp;""
        		  *    this.sNetworkName == &amp;""
        		  *    this.sServerName == &amp;""
        		  *    this.cMyself.myAwayReason == &amp;""
        		  *    this.cMyself.sRealName == &amp;""
        		  *    ...
        		  * 
        		  *  Test Vectors:
        		  *    this.pingTimer: Addr_Set{null}, Inverse{null}
        		  */
   562  		triedAlt = false;
   563  		got001 = false;
   564  		post005 = false;
   565  		// Clear the hash tables
   566  		hChannelList.clear();
   567  		hClientList.clear();
   568  		h005Info.clear();
   569  		hPrefixModes.clear();
   570  		hPrefixMap.clear();
   571  		hChanModesOther.clear();
   572  		hChanModesBool.clear();
   573  		hUserModes.clear();
   574  		hChanPrefix.clear();
   575  		// Reset the mode indexes
   576  		nNextKeyPrefix = 1;
   577  		nNextKeyCMBool = 1;
   578  		nNextKeyUser = 1;
   579  		sServerName = "";
   580  		sNetworkName = "";
   581  		lastLine = "";
   582  		cMyself = new ClientInfo(this, "myself").setFake(true);
   583  
   584          pingTimerSem.acquireUninterruptibly();
   585  		if (pingTimer != null) {
   586  			pingTimer.cancel();
   587  			pingTimer = null;
   588  		}
   589          pingTimerSem.release();
   590  
   591  		currentSocketState = SocketState.CLOSED;
   592  		// Char Mapping
   593  		updateCharArrays((byte)4);
   594  	}
   595  
   596  	/**
   597  	 * Called after other error callbacks.
   598  	 * CallbackOnErrorInfo automatically calls this *AFTER* any registered callbacks
   599  	 * for it are called.
   600  	 *
   601  	 * @param errorInfo ParserError object representing the error.
   602  	 * @param called True/False depending on the the success of other callbacks.
   603  	 */
   604  	public void onPostErrorInfo(final ParserError errorInfo, final boolean called) {
        		 /* 
    P/P 		  *  Method: void onPostErrorInfo(ParserError, bool)
        		  * 
        		  *  Preconditions:
        		  *    errorInfo != null
        		  *    init'ed(errorInfo.errorLevel)
        		  *    (soft) init'ed(this.cMyself)
        		  *    (soft) init'ed(this.currentSocketState)
        		  *    (soft) init'ed(this.pingTimer)
        		  *    (soft) init'ed(this.stringConverter)
        		  *    (soft) init'ed(this.disconnectOnFatal)
        		  * 
        		  *  Postconditions:
        		  *    this.cMyself == old this.cMyself
        		  *    this.currentSocketState == old this.currentSocketState
        		  *    this.got001 == old this.got001
        		  *    this.lastLine == old this.lastLine
        		  *    this.nNextKeyCMBool == old this.nNextKeyCMBool
        		  *    this.nNextKeyPrefix == old this.nNextKeyPrefix
        		  *    this.nNextKeyUser == old this.nNextKeyUser
        		  *    this.pingTimer == old this.pingTimer
        		  *    this.post005 == old this.post005
        		  *    this.sNetworkName == old this.sNetworkName
        		  *    ...
        		  * 
        		  *  Test Vectors:
        		  *    errorInfo.errorLevel mod 2: {0}, {1}
        		  *    this.disconnectOnFatal: {0}, {1}
        		  */
   605  		if (errorInfo.isFatal() && disconnectOnFatal) {
   606  			disconnect("Fatal Parser Error");
   607  		}
   608  	}
   609  
   610  	/**
   611  	 * Get the current Value of disconnectOnFatal.
   612  	 *
   613  	 * @return Value of disconnectOnFatal (true if the parser automatically disconnects on fatal errors, else false)
   614  	 */
        	 /* 
    P/P 	  *  Method: bool getDisconnectOnFatal()
        	  * 
        	  *  Preconditions:
        	  *    init'ed(this.disconnectOnFatal)
        	  * 
        	  *  Postconditions:
        	  *    return_value == this.disconnectOnFatal
        	  *    init'ed(return_value)
        	  */
   615  	public boolean getDisconnectOnFatal() { return disconnectOnFatal; }
   616  
   617  	/**
   618  	 * Set the current Value of disconnectOnFatal.
   619  	 *
   620  	 * @param newValue New value to set disconnectOnFatal
   621  	 */
        	 /* 
    P/P 	  *  Method: void setDisconnectOnFatal(bool)
        	  * 
        	  *  Postconditions:
        	  *    this.disconnectOnFatal == newValue
        	  *    init'ed(this.disconnectOnFatal)
        	  */
   622  	public void setDisconnectOnFatal(final boolean newValue) { disconnectOnFatal = newValue; }
   623  
   624  	/**
   625  	 * Connect to IRC.
   626  	 *
   627  	 * @throws IOException if the socket can not be connected
   628  	 * @throws UnknownHostException if the hostname can not be resolved
   629  	 * @throws NoSuchAlgorithmException if SSL is not available
   630  	 * @throws KeyManagementException if the trustManager is invalid
   631  	 */
   632  	private void connect() throws UnknownHostException, IOException, NoSuchAlgorithmException, KeyManagementException {
        		 /* 
    P/P 		  *  Method: void connect()
        		  * 
        		  *  Preconditions:
        		  *    init'ed(this.pingTimer)
        		  *    this.h005Info != null
        		  *    this.hChanModesBool != null
        		  *    this.hChanModesOther != null
        		  *    this.hChanPrefix != null
        		  *    this.hChannelList != null
        		  *    this.hClientList != null
        		  *    this.hPrefixMap != null
        		  *    this.hPrefixModes != null
        		  *    this.hUserModes != null
        		  *    ...
        		  * 
        		  *  Presumptions:
        		  *    getIRCAuthenticator(...).replies != null
        		  *    init'ed(java.net.Proxy$Type.SOCKS)
        		  *    javax.net.ssl.SSLContext:getInstance(...)@680 != null
        		  *    javax.net.ssl.SSLContext:getSocketFactory(...)@683 != null
        		  *    javax.net.ssl.SSLSocketFactory:createSocket(...)@685 != null
        		  *    ...
        		  * 
        		  *  Postconditions:
        		  *    com/dmdirc/parser/irc/IRCAuthenticator.me == One-of{old com/dmdirc/parser/irc/IRCAuthenticator.me, &amp;new IRCAuthenticator(getIRCAuthenticator#1)}
        		  *    init'ed(com/dmdirc/parser/irc/IRCAuthenticator.me)
        		  *    this.cMyself == &amp;new ClientInfo(resetState#1)
        		  *    this.currentSocketState in Addr_Set{&amp;com.dmdirc.parser.irc.SocketState__static_init.new SocketState(SocketState__static_init#2),&amp;com.dmdirc.parser.irc.SocketState__static_init.new SocketState(SocketState__static_init#3)}
        		  *    this.got001 == 0
        		  *    this.post005 == 0
        		  *    this.triedAlt == 0
        		  *    this.in == &amp;new BufferedReader(connect#19)
        		  *    this.lastLine == &amp;""
        		  *    this.sNetworkName == &amp;""
        		  *    ...
        		  * 
        		  *  Test Vectors:
        		  *    this.myTrustManager: Inverse{null}, Addr_Set{null}
        		  *    this.bindIP: Addr_Set{null}, Inverse{null}
        		  *    this.server.isSSL: {1}, {0}
        		  *    this.server.proxyUser: Addr_Set{null}, Inverse{null}
        		  *    this.server.useSocksProxy: {0}, {1}
        		  *    java.lang.String:isEmpty(...)@642: {1}, {0}
        		  *    java.lang.String:isEmpty(...)@652: {1}, {0}
        		  *    java.lang.String:isEmpty(...)@661: {1}, {0}
        		  *    java.lang.String:isEmpty(...)@687: {0}, {1}
        		  */
   633  		resetState();
   634  		callDebugInfo(DEBUG_SOCKET, "Connecting to " + server.getHost() + ":" + server.getPort());
   635  
   636  		if (server.getPort() > 65535 || server.getPort() <= 0) {
   637  			throw new IOException("Server port ("+server.getPort()+") is invalid.");
   638  		}
   639  
   640  		if (server.getUseSocks()) {
   641  			callDebugInfo(DEBUG_SOCKET, "Using Proxy");
   642  			if (bindIP != null && !bindIP.isEmpty()) {
   643  				callDebugInfo(DEBUG_SOCKET, "IP Binding is not possible when using a proxy.");
   644  			}
   645  			if (server.getProxyPort() > 65535 || server.getProxyPort() <= 0) {
   646  				throw new IOException("Proxy port ("+server.getProxyPort()+") is invalid.");
   647  			}
   648  
   649  			final Proxy.Type proxyType = Proxy.Type.SOCKS;
   650  			socket = new Socket(new Proxy(proxyType, new InetSocketAddress(server.getProxyHost(), server.getProxyPort())));
   651  			currentSocketState = SocketState.OPEN;
   652  			if (server.getProxyUser() != null && !server.getProxyUser().isEmpty()) {
   653  				IRCAuthenticator.getIRCAuthenticator().addAuthentication(server);
   654  			}
   655  			socket.connect(new InetSocketAddress(server.getHost(), server.getPort()));
   656  		} else {
   657  			callDebugInfo(DEBUG_SOCKET, "Not using Proxy");
   658  			if (!server.getSSL()) {
   659  				socket = new Socket();
   660  
   661  				if (bindIP != null && !bindIP.isEmpty()) {
   662  					callDebugInfo(DEBUG_SOCKET, "Binding to IP: "+bindIP);
   663  					try {
   664  						socket.bind(new InetSocketAddress(InetAddress.getByName(bindIP), 0));
   665  					} catch (IOException e) {
   666  						callDebugInfo(DEBUG_SOCKET, "Binding failed: "+e.getMessage());
   667  					}
   668  				}
   669  
   670  				currentSocketState = SocketState.OPEN;
   671  				socket.connect(new InetSocketAddress(server.getHost(), server.getPort()));
   672  			}
   673  		}
   674  
   675  		if (server.getSSL()) {
   676  			callDebugInfo(DEBUG_SOCKET, "Server is SSL.");
   677  
   678  			if (myTrustManager == null) { myTrustManager = trustAllCerts; }
   679  
   680  			final SSLContext sc = SSLContext.getInstance("SSL");
   681  			sc.init(myKeyManagers, myTrustManager, new java.security.SecureRandom());
   682  
   683  			final SSLSocketFactory socketFactory = sc.getSocketFactory();
   684  			if (server.getUseSocks()) {
   685  				socket = socketFactory.createSocket(socket, server.getHost(), server.getPort(), false);
   686  			} else {
   687  				if (bindIP == null || bindIP.isEmpty()) {
   688  					socket = socketFactory.createSocket(server.getHost(), server.getPort());
   689  				} else {
   690  					callDebugInfo(DEBUG_SOCKET, "Binding to IP: "+bindIP);
   691  					try {
   692  						socket = socketFactory.createSocket(server.getHost(), server.getPort(), InetAddress.getByName(bindIP), 0);
   693  					} catch (UnknownHostException e) {
   694  						callDebugInfo(DEBUG_SOCKET, "Bind failed: "+e.getMessage());
   695  						socket = socketFactory.createSocket(server.getHost(), server.getPort());
   696  					}
   697  				}
   698  			}
   699  
   700  			currentSocketState = SocketState.OPEN;
   701  		}
   702  
   703  		callDebugInfo(DEBUG_SOCKET, "\t-> Opening socket output stream PrintWriter");
   704  		out = new PrintWriter(socket.getOutputStream(), true);
   705  		callDebugInfo(DEBUG_SOCKET, "\t-> Opening socket input stream BufferedReader");
   706  		in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
   707  		callDebugInfo(DEBUG_SOCKET, "\t-> Socket Opened");
   708  	}
   709  
   710  	/**
   711  	 * Send server connection strings (NICK/USER/PASS).
   712  	 */
   713  	protected void sendConnectionStrings() {
        		 /* 
    P/P 		  *  Method: void sendConnectionStrings()
        		  * 
        		  *  Preconditions:
        		  *    init'ed(this.me.nickname)
        		  *    init'ed(this.currentSocketState)
        		  *    this.me != null
        		  *    init'ed(this.me.realname)
        		  *    init'ed(this.me.username)
        		  *    this.server != null
        		  *    init'ed(this.server.host)
        		  *    this.server.password != null
        		  *    (soft) init'ed(this.stringConverter)
        		  *    (soft) this.cMyself != null
        		  *    ...
        		  * 
        		  *  Presumptions:
        		  *    java.net.InetAddress:getLocalHost(...)@720 != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(java.lang.String:substring(...)._tainted)
        		  *    possibly_updated(this.cMyself.myAwayReason)
        		  *    this.me.nickname == old this.me.nickname
        		  *    init'ed(this.me.nickname)
        		  *    this.sThinkNickname == One-of{old this.sThinkNickname, old this.me.nickname}
        		  *    this.stringConverter == One-of{old this.stringConverter, &amp;new IRCStringConverter(getIRCStringConverter#1)}
        		  *    init'ed(this.stringConverter)
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    new char[](IRCStringConverter#1) num objects == new IRCStringConverter(getIRCStringConverter#1) num objects
        		  *    new char[](IRCStringConverter#2) num objects == new IRCStringConverter(getIRCStringConverter#1) num objects
        		  *    ...
        		  * 
        		  *  Test Vectors:
        		  *    java.lang.String:isEmpty(...)@714: {1}, {0}
        		  */
   714  		if (!server.getPassword().isEmpty()) {
   715  			sendString("PASS " + server.getPassword());
   716  		}
   717  		setNickname(me.getNickname());
   718  		String localhost;
   719  		try {
   720  			localhost = InetAddress.getLocalHost().getHostAddress();
   721  		} catch (UnknownHostException uhe) {
   722  			localhost = "*";
   723  		}
   724  		sendString("USER " + me.getUsername() + " "+localhost+" "+server.getHost()+" :" + me.getRealname());
   725  	}
   726  
   727  	/**
   728  	 * Handle an onConnect error.
   729  	 *
   730  	 * @param e Exception to handle
   731  	 */
   732  	private void handleConnectException(final Exception e) {
        		 /* 
    P/P 		  *  Method: void handleConnectException(Exception)
        		  * 
        		  *  Preconditions:
        		  *    e != null
        		  *    init'ed(this.currentSocketState)
        		  *    init'ed(this.lastLine)
        		  *    init'ed(this.pingTimer)
        		  *    this.h005Info != null
        		  *    this.hChanModesBool != null
        		  *    this.hChanModesOther != null
        		  *    this.hChanPrefix != null
        		  *    this.hChannelList != null
        		  *    this.hClientList != null
        		  *    ...
        		  * 
        		  *  Postconditions:
        		  *    this.cMyself == &amp;new ClientInfo(resetState#1)
        		  *    this.currentSocketState == &amp;com.dmdirc.parser.irc.SocketState__static_init.new SocketState(SocketState__static_init#2)
        		  *    this.got001 == 0
        		  *    this.post005 == 0
        		  *    this.triedAlt == 0
        		  *    this.lastLine == &amp;""
        		  *    this.sNetworkName == &amp;""
        		  *    this.sServerName == &amp;""
        		  *    this.cMyself.myAwayReason == &amp;""
        		  *    this.cMyself.sRealName == &amp;""
        		  *    ...
        		  */
   733  		callDebugInfo(DEBUG_SOCKET, "Error Connecting (" + e.getMessage() + "), Aborted");
   734  		final ParserError ei = new ParserError(ParserError.ERROR_ERROR, "Exception with server socket", getLastLine());
   735  		ei.setException(e);
   736  		callConnectError(ei);
   737  		
   738  		if (currentSocketState != SocketState.CLOSED) {
   739  			currentSocketState = SocketState.CLOSED;
   740  			callSocketClosed();
   741  		}
   742  		resetState();
   743  	}
   744  
   745  	/**
   746  	 * Begin execution.
   747  	 * Connect to server, and start parsing incomming lines
   748  	 */
   749  	@Override
   750  	public void run() {
        		 /* 
    P/P 		  *  Method: void run()
        		  * 
        		  *  Preconditions:
        		  *    init'ed(this.hasBegan)
        		  *    this.myCallbackManager != null
        		  *    this.myCallbackManager.callbackHash != null
        		  *    (soft) init'ed(com/dmdirc/parser/irc/IRCAuthenticator.me)
        		  *    (soft) init'ed(this.currentSocketState)
        		  *    (soft) init'ed(this.lastLine)
        		  *    (soft) this.lastPingValue != null
        		  *    (soft) init'ed(this.me.nickname)
        		  *    (soft) init'ed(this.myTrustManager)
        		  *    (soft) init'ed(this.pingTimer)
        		  *    ...
        		  * 
        		  *  Presumptions:
        		  *    new ClientInfo(resetState#1).sNickname@754 != null
        		  * 
        		  *  Postconditions:
        		  *    com/dmdirc/parser/irc/IRCAuthenticator.me == One-of{old com/dmdirc/parser/irc/IRCAuthenticator.me, &amp;new IRCAuthenticator(getIRCAuthenticator#1)}
        		  *    init'ed(com/dmdirc/parser/irc/IRCAuthenticator.me)
        		  *    init'ed(java.lang.String:substring(...)._tainted)
        		  *    possibly_updated(java.lang.String:substring(...)._tainted)
        		  *    this.cMyself == One-of{old this.cMyself, &amp;new ClientInfo(resetState#1)}
        		  *    init'ed(this.cMyself.myAwayReason)
        		  *    this.currentSocketState == One-of{old this.currentSocketState, &amp;com.dmdirc.parser.irc.SocketState__static_init.new SocketState(SocketState__static_init#2)}
        		  *    init'ed(this.currentSocketState)
        		  *    possibly_updated(this.got001)
        		  *    this.hasBegan == 1
        		  *    ...
        		  * 
        		  *  Test Vectors:
        		  *    this.hasBegan: {0}, {1}
        		  *    java.io.BufferedReader:readLine(...)@775: Inverse{null}, Addr_Set{null}
        		  */
   751  		callDebugInfo(DEBUG_INFO, "Begin Thread Execution");
   752  		if (hasBegan) { return; } else { hasBegan = true; }
   753  		try {
   754  		 connect();
   755  		} catch (UnknownHostException e) {
   756  			handleConnectException(e);
   757  			return;
   758  		} catch (IOException e) {
   759  			handleConnectException(e);
   760  			return;
   761  		} catch (NoSuchAlgorithmException e) {
   762  			handleConnectException(e);
   763  			return;
   764  		} catch (KeyManagementException e) {
   765  			handleConnectException(e);
   766  			return;
   767  		}
   768  
   769  		callDebugInfo(DEBUG_SOCKET, "Socket Connected");
   770  
   771  		sendConnectionStrings();
   772  
   773  		while (true) {
   774  			try {
   775  				lastLine = in.readLine(); // Blocking :/
   776  				if (lastLine == null) {
   777  					if (currentSocketState != SocketState.CLOSED) {
   778  						currentSocketState = SocketState.CLOSED;
   779  						callSocketClosed();
   780  					}
   781  					resetState();
   782  					break;
   783  				} else {
   784  					processLine(lastLine);
   785  				}
   786  			} catch (IOException e) {
   787  				callDebugInfo(DEBUG_SOCKET, "Exception in main loop (" + e.getMessage() + "), Aborted");
   788  				
   789  				if (currentSocketState != SocketState.CLOSED) {
   790  					currentSocketState = SocketState.CLOSED;
   791  					callSocketClosed();
   792  				}
   793  				resetState();
   794  				break;
   795  			}
   796  		}
   797  		callDebugInfo(DEBUG_INFO, "End Thread Execution");
   798  	}
   799  
   800  	/**
   801  	 * Get the current local port number.
   802  	 *
   803  	 * @return 0 if not connected, else the current local port number
   804  	 */
   805  	public int getLocalPort() {
        		 /* 
    P/P 		  *  Method: int getLocalPort()
        		  * 
        		  *  Preconditions:
        		  *    init'ed(this.currentSocketState)
        		  *    (soft) this.socket != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  */
   806  		if (currentSocketState == SocketState.OPEN) {
   807  			return socket.getLocalPort();
   808  		} else {
   809  			return 0;
   810  		}
   811  	}
   812  
   813  	/** Close socket on destroy. */
   814  	@Override
   815  	protected void finalize() throws Throwable {
        		 /* 
    P/P 		  *  Method: void finalize()
        		  * 
        		  *  Preconditions:
        		  *    (soft) this.myCallbackManager != null
        		  *    (soft) this.myCallbackManager.callbackHash != null
        		  *    (soft) this.socket != null
        		  */
   816  		try { socket.close(); }
   817  		catch (IOException e) {
   818  			callDebugInfo(DEBUG_SOCKET, "Could not close socket");
   819  		}
   820  		super.finalize();
   821  	}
   822  
   823  	/**
   824  	 * Get the trailing parameter for a line.
   825  	 * The parameter is everything after the first occurance of " :" ot the last token in the line after a space.
   826  	 *
   827  	 * @param line Line to get parameter for
   828  	 * @return Parameter of the line
   829  	 */
   830  	public static String getParam(final String line) {
        		 /* 
    P/P 		  *  Method: String getParam(String)
        		  *    getParam fails for all possible inputs
        		  * 
        		  *  Preconditions:
        		  *    (soft) line != null
        		  * 
        		  *  Postconditions:
        		  *    return_value == undefined
        		  *    return_value == null
        		  */
   831  		String[] params = null;
   832  		params = line.split(" :", 2);
   833  		return params[params.length - 1];
   834  	}
   835  
   836  	/**
   837  	 * Tokenise a line.
   838  	 * splits by " " up to the first " :" everything after this is a single token
   839  	 *
   840  	 * @param line Line to tokenise
   841  	 * @return Array of tokens
   842  	 */
   843  	public static String[] tokeniseLine(final String line) {
        		 /* 
    P/P 		  *  Method: String[] tokeniseLine(String)
        		  * 
        		  *  Presumptions:
        		  *    java.lang.String:indexOf(...)@848 <= 232-3
        		  * 
        		  *  Postconditions:
        		  *    init'ed(java.lang.String:split(...)._tainted)
        		  *    java.lang.String:split(...)[...] == null
        		  *    init'ed(java.lang.String:substring(...)._tainted)
        		  *    return_value in Addr_Set{&amp;java.lang.String:split(...),&amp;new String[](tokeniseLine#2),&amp;new String[](tokeniseLine#1)}
        		  *    new String[](tokeniseLine#1) num objects <= 1
        		  *    new String[](tokeniseLine#1).length == 1
        		  *    new String[](tokeniseLine#1)[0] == &amp;""
        		  *    new String[](tokeniseLine#2) num objects <= 1
        		  *    new String[](tokeniseLine#2).length == 1
        		  *    new String[](tokeniseLine#2)[...] == null
        		  * 
        		  *  Test Vectors:
        		  *    line: Inverse{null}, Addr_Set{null}
        		  *    java.lang.String:indexOf(...)@848: {-231..-1}, {0..232-3}
        		  */
   844  		if (line == null) {
   845  			return new String[]{"", }; // Return empty string[]
   846  		}
   847  
   848  		final int lastarg = line.indexOf(" :");
   849  		String[] tokens;
   850  
   851  		if (lastarg > -1) {
   852  			final String[] temp = line.substring(0, lastarg).split(" ");
   853  			tokens = new String[temp.length + 1];
   854  			System.arraycopy(temp, 0, tokens, 0, temp.length);
   855  			tokens[temp.length] = line.substring(lastarg + 2);
   856  		} else {
   857  			tokens = line.split(" ");
   858  		}
   859  
   860  		return tokens;
   861  	}
   862  
   863  	/**
   864  	 * Get the ClientInfo object for a person.
   865  	 *
   866  	 * @param sHost Who can be any valid identifier for a client as long as it contains a nickname (?:)nick(?!ident)(?@host)
   867  	 * @return ClientInfo Object for the client, or null
   868  	 */
   869  	public ClientInfo getClientInfo(final String sHost) {
        		 /* 
    P/P 		  *  Method: ClientInfo getClientInfo(String)
        		  * 
        		  *  Preconditions:
        		  *    init'ed(this.stringConverter)
        		  *    sHost != null
        		  *    this.hClientList != null
        		  *    (soft) this.stringConverter.lowercase != null
        		  *    (soft) init'ed(this.stringConverter.lowercase[...])
        		  * 
        		  *  Presumptions:
        		  *    parseHost(...)@870 init'ed
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  *    this.stringConverter == One-of{old this.stringConverter, &amp;new IRCStringConverter(getIRCStringConverter#1)}
        		  *    this.stringConverter != null
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    new char[](IRCStringConverter#1) num objects == new IRCStringConverter(getIRCStringConverter#1) num objects
        		  *    new char[](IRCStringConverter#2) num objects == new IRCStringConverter(getIRCStringConverter#1) num objects
        		  *    new IRCStringConverter(getIRCStringConverter#1).limit == 4
        		  *    new IRCStringConverter(getIRCStringConverter#1).lowercase == &amp;new char[](IRCStringConverter#1)
        		  *    new IRCStringConverter(getIRCStringConverter#1).uppercase == &amp;new char[](IRCStringConverter#2)
        		  *    new char[](IRCStringConverter#1).length == 127
        		  *    ...
        		  * 
        		  *  Test Vectors:
        		  *    java.util.Map:containsKey(...)@871: {0}, {1}
        		  */
   870  		final String sWho = getIRCStringConverter().toLowerCase(ClientInfo.parseHost(sHost));
   871  		if (hClientList.containsKey(sWho)) { return hClientList.get(sWho); }
   872  		else { return null; }
   873  	}
   874  
   875  	/**
   876  	 * Get the ClientInfo object for a person, or create a fake client info object.
   877  	 *
   878  	 * @param sHost Who can be any valid identifier for a client as long as it contains a nickname (?:)nick(?!ident)(?@host)
   879  	 * @return ClientInfo Object for the client.
   880  	 */
   881  	public ClientInfo getClientInfoOrFake(final String sHost) {
        		 /* 
    P/P 		  *  Method: ClientInfo getClientInfoOrFake(String)
        		  * 
        		  *  Preconditions:
        		  *    init'ed(this.stringConverter)
        		  *    sHost != null
        		  *    this.hClientList != null
        		  *    (soft) this.stringConverter.lowercase != null
        		  *    (soft) init'ed(this.stringConverter.lowercase[...])
        		  * 
        		  *  Presumptions:
        		  *    parseHost(...)@882 init'ed
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  *    this.stringConverter == One-of{old this.stringConverter, &amp;new IRCStringConverter(getIRCStringConverter#1)}
        		  *    this.stringConverter != null
        		  *    new ClientInfo(getClientInfoOrFake#1) num objects <= 1
        		  *    new ClientInfo(getClientInfoOrFake#1).bIsFake == 1
        		  *    new ClientInfo(getClientInfoOrFake#1).lModeQueue == &amp;new LinkedList(ClientInfo#2)
        		  *    new ClientInfo(getClientInfoOrFake#1).myAwayReason == &amp;""
        		  *    new ClientInfo(getClientInfoOrFake#1).myChannelClientInfos == &amp;new Hashtable(ClientInfo#1)
        		  *    new ClientInfo(getClientInfoOrFake#1).myMap == &amp;new HashMap(ClientInfo#3)
        		  *    new ClientInfo(getClientInfoOrFake#1).myParser == this
        		  *    ...
        		  * 
        		  *  Test Vectors:
        		  *    java.util.Map:containsKey(...)@883: {0}, {1}
        		  */
   882  		final String sWho = getIRCStringConverter().toLowerCase(ClientInfo.parseHost(sHost));
   883  		if (hClientList.containsKey(sWho)) { return hClientList.get(sWho); }
   884  		else { return new ClientInfo(this, sHost).setFake(true); }
   885  	}
   886  
   887  	/**
   888  	 * Get the ChannelInfo object for a channel.
   889  	 *
   890  	 * @param sWhat This is the name of the channel.
   891  	 * @return ChannelInfo Object for the channel, or null
   892  	 */
   893  	public ChannelInfo getChannelInfo(String sWhat) {
        		 /* 
    P/P 		  *  Method: ChannelInfo getChannelInfo(String)
        		  * 
        		  *  Preconditions:
        		  *    init'ed(this.stringConverter)
        		  *    sWhat != null
        		  *    this.hChannelList != null
        		  *    (soft) this.stringConverter.lowercase != null
        		  *    (soft) init'ed(this.stringConverter.lowercase[...])
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  *    this.stringConverter == One-of{old this.stringConverter, &amp;new IRCStringConverter(getIRCStringConverter#1)}
        		  *    this.stringConverter != null
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    new IRCStringConverter(getIRCStringConverter#1).limit == 4
        		  *    new IRCStringConverter(getIRCStringConverter#1).lowercase == &amp;new char[](IRCStringConverter#1)
        		  *    new IRCStringConverter(getIRCStringConverter#1).uppercase == &amp;new char[](IRCStringConverter#2)
        		  *    new char[](IRCStringConverter#1) num objects <= 1
        		  *    new char[](IRCStringConverter#1).length == 127
        		  *    possibly_updated(new char[](IRCStringConverter#1)[...])
        		  *    ...
        		  * 
        		  *  Test Vectors:
        		  *    java.util.Map:containsKey(...)@896: {0}, {1}
        		  */
   894  		synchronized (hChannelList) {
   895  			sWhat = getIRCStringConverter().toLowerCase(sWhat);
   896  			if (hChannelList.containsKey(sWhat)) { return hChannelList.get(sWhat); } else { return null; }
   897  		}
   898  	}
   899  
   900  	/**
   901  	 * Send a line to the server.
   902  	 *
   903  	 * @param line Line to send (\r\n termination is added automatically)
   904  	 */
        	 /* 
    P/P 	  *  Method: void sendLine(String)
        	  * 
        	  *  Preconditions:
        	  *    init'ed(this.out)
        	  *    (soft) init'ed(this.stringConverter)
        	  *    (soft) this.cMyself != null
        	  *    (soft) init'ed(this.currentSocketState)
        	  *    (soft) this.hChanModesOther != null
        	  *    (soft) this.hChannelList != null
        	  *    (soft) this.myCallbackManager != null
        	  *    (soft) this.myCallbackManager.callbackHash != null
        	  * 
        	  *  Postconditions:
        	  *    init'ed(java.lang.String:substring(...)._tainted)
        	  *    possibly_updated(this.cMyself.myAwayReason)
        	  *    this.stringConverter == One-of{old this.stringConverter, &amp;new IRCStringConverter(getIRCStringConverter#1)}
        	  *    init'ed(this.stringConverter)
        	  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        	  *    new char[](IRCStringConverter#1) num objects == new IRCStringConverter(getIRCStringConverter#1) num objects
        	  *    new char[](IRCStringConverter#2) num objects == new IRCStringConverter(getIRCStringConverter#1) num objects
        	  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).limit)
        	  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).lowercase)
        	  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).uppercase)
        	  *    ...
        	  */
   905  	public void sendLine(final String line) { doSendString(line, false); }
   906  
   907  	/**
   908  	 * Send a line to the server and add proper line ending.
   909  	 *
   910  	 * @param line Line to send (\r\n termination is added automatically)
   911  	 */
        	 /* 
    P/P 	  *  Method: void sendString(String)
        	  * 
        	  *  Preconditions:
        	  *    init'ed(this.out)
        	  *    (soft) init'ed(this.stringConverter)
        	  *    (soft) this.cMyself != null
        	  *    (soft) init'ed(this.currentSocketState)
        	  *    (soft) this.hChanModesOther != null
        	  *    (soft) this.hChannelList != null
        	  *    (soft) this.myCallbackManager != null
        	  *    (soft) this.myCallbackManager.callbackHash != null
        	  * 
        	  *  Postconditions:
        	  *    init'ed(java.lang.String:substring(...)._tainted)
        	  *    possibly_updated(this.cMyself.myAwayReason)
        	  *    this.stringConverter == One-of{old this.stringConverter, &amp;new IRCStringConverter(getIRCStringConverter#1)}
        	  *    init'ed(this.stringConverter)
        	  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        	  *    new char[](IRCStringConverter#1) num objects == new IRCStringConverter(getIRCStringConverter#1) num objects
        	  *    new char[](IRCStringConverter#2) num objects == new IRCStringConverter(getIRCStringConverter#1) num objects
        	  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).limit)
        	  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).lowercase)
        	  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).uppercase)
        	  *    ...
        	  */
   912  	protected void sendString(final String line) { doSendString(line, true); }
   913  
   914  	/**
   915  	 * Send a line to the server and add proper line ending.
   916  	 *
   917  	 * @param line Line to send (\r\n termination is added automatically)
   918  	 * @param fromParser is this line from the parser? (used for callDataOut)
   919  	 */
   920  	protected void doSendString(final String line, final boolean fromParser) {
        		 /* 
    P/P 		  *  Method: void doSendString(String, bool)
        		  * 
        		  *  Preconditions:
        		  *    init'ed(this.out)
        		  *    (soft) init'ed(this.stringConverter)
        		  *    (soft) this.cMyself != null
        		  *    (soft) init'ed(this.currentSocketState)
        		  *    (soft) this.hChanModesOther != null
        		  *    (soft) this.hChannelList != null
        		  *    (soft) this.myCallbackManager != null
        		  *    (soft) this.myCallbackManager.callbackHash != null
        		  * 
        		  *  Presumptions:
        		  *    channel.myParser.myCallbackManager.callbackHash@932 != null
        		  *    channel.myParser.myCallbackManager@932 != null
        		  *    channel.myParser@932 != null
        		  *    java.util.Map:get(...)@938 != null
        		  *    newLine.length in {1..232}
        		  *    ...
        		  * 
        		  *  Postconditions:
        		  *    init'ed(java.lang.String:substring(...)._tainted)
        		  *    possibly_updated(this.cMyself.myAwayReason)
        		  *    this.stringConverter == One-of{old this.stringConverter, &amp;new IRCStringConverter(getIRCStringConverter#1)}
        		  *    init'ed(this.stringConverter)
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).limit)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).lowercase)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).uppercase)
        		  *    new char[](IRCStringConverter#1) num objects <= 1
        		  *    init'ed(new char[](IRCStringConverter#1).length)
        		  *    ...
        		  * 
        		  *  Test Vectors:
        		  *    this.out: Addr_Set{null}, Inverse{null}
        		  *    java.lang.Byte:byteValue(...)@938: {-128..0, 2..255}, {1}
        		  *    java.lang.String:equalsIgnoreCase(...)@925: {0}, {1}
        		  *    java.lang.String:equalsIgnoreCase(...)@927: {0}, {1}
        		  *    java.util.LinkedList:contains(...)@939: {0}, {1}
        		  *    java.util.Map:containsKey(...)@938: {0}, {1}
        		  */
   921  		if (out == null || getSocketState() != SocketState.OPEN) { return; }
   922  		callDataOut(line, fromParser);
   923  		out.printf("%s\r\n", line);
   924  		final String[] newLine = tokeniseLine(line);
   925  		if (newLine[0].equalsIgnoreCase("away") && newLine.length > 1) {
   926  			cMyself.setAwayReason(newLine[newLine.length-1]);
   927  		} else if (newLine[0].equalsIgnoreCase("mode") && newLine.length == 3) {
   928  			// This makes sure we don't add the same item to the LMQ twice, even if its requested twice,
   929  			// as the ircd will only reply once.
   930  			final LinkedList<Character> foundModes = new LinkedList<Character>();
   931  
   932  			final ChannelInfo channel = getChannelInfo(newLine[1]);
   933  			if (channel != null) {
   934  				final Queue<Character> listModeQueue = channel.getListModeQueue();
   935  				for (int i = 0; i < newLine[2].length() ; ++i) {
   936  					final Character mode = newLine[2].charAt(i);
   937  					callDebugInfo(DEBUG_LMQ, "Intercepted mode request for "+channel+" for mode "+mode);
   938  					if (hChanModesOther.containsKey(mode) && hChanModesOther.get(mode) == MODE_LIST) {
   939  						if (foundModes.contains(mode)) {
   940  							callDebugInfo(DEBUG_LMQ, "Already added to LMQ");
   941  						} else {
   942  							listModeQueue.offer(mode);
   943  							foundModes.offer(mode);
   944  							callDebugInfo(DEBUG_LMQ, "Added to LMQ");
   945  						}
   946  					}
   947  				}
   948  			}
   949  		}
   950  	}
   951  
   952  	/**
   953  	 * Get the network name given in 005.
   954  	 *
   955  	 * @return network name from 005
   956  	 */
   957  	public String getNetworkName() {
        		 /* 
    P/P 		  *  Method: String getNetworkName()
        		  * 
        		  *  Preconditions:
        		  *    init'ed(this.sNetworkName)
        		  * 
        		  *  Postconditions:
        		  *    return_value == this.sNetworkName
        		  *    init'ed(return_value)
        		  */
   958  		return sNetworkName;
   959  	}
   960  
   961  	/**
   962  	 * Get the server name given in 001.
   963  	 *
   964  	 * @return server name from 001
   965  	 */
   966  	public String getServerName() {
        		 /* 
    P/P 		  *  Method: String getServerName()
        		  * 
        		  *  Preconditions:
        		  *    init'ed(this.sServerName)
        		  * 
        		  *  Postconditions:
        		  *    return_value == this.sServerName
        		  *    init'ed(return_value)
        		  */
   967  		return sServerName;
   968  	}
   969  
   970  	/**
   971  	 * Get the last line of input recieved from the server.
   972  	 *
   973  	 * @return the last line of input recieved from the server.
   974  	 */
   975  	public String getLastLine() {
        		 /* 
    P/P 		  *  Method: String getLastLine()
        		  * 
        		  *  Preconditions:
        		  *    init'ed(this.lastLine)
        		  * 
        		  *  Postconditions:
        		  *    return_value == this.lastLine
        		  *    init'ed(return_value)
        		  */
   976  		return lastLine;
   977  	}
   978  
   979  	/**
   980  	 * Process a line and call relevent methods for handling.
   981  	 *
   982  	 * @param line IRC Line to process
   983  	 */
   984  	protected void processLine(final String line) {
        		 /* 
    P/P 		  *  Method: void processLine(String)
        		  * 
        		  *  Preconditions:
        		  *    this.myCallbackManager != null
        		  *    this.myCallbackManager.callbackHash != null
        		  *    this.pingNeeded != null
        		  *    (soft) this.lastPingValue != null
        		  *    (soft) init'ed(this.post005)
        		  *    (soft) init'ed(this.stringConverter)
        		  *    (soft) init'ed(this...lastLine)
        		  *    (soft) this...myCallbackManager != null
        		  *    (soft) this...myCallbackManager.callbackHash != null
        		  *    (soft) this.cMyself != null
        		  *    ...
        		  * 
        		  *  Presumptions:
        		  *    java.lang.System:currentTimeMillis(...)@1002 - this.pingTime in {-263..264-1}
        		  *    token.length <= 232-1
        		  *    token[0] != null
        		  *    token[1] != null
        		  *    token[3] != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(java.lang.String:substring(...)._tainted)
        		  *    possibly_updated(this.cMyself.myAwayReason)
        		  *    possibly_updated(this.lastPingValue)
        		  *    possibly_updated(this.post005)
        		  *    possibly_updated(this.serverLag)
        		  *    possibly_updated(this.stringConverter)
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).limit)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).lowercase)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).uppercase)
        		  *    ...
        		  * 
        		  *  Test Vectors:
        		  *    this.post005: {1}, {0}
        		  *    this.got001: {0}, {1}
        		  *    java.lang.Integer:parseInt(...)@1018: {0..5}, {6..232-1}
        		  *    java.lang.String:charAt(...)@1037: {0, 2..216-1}, {1}
        		  *    java.lang.String:equals(...)@1000: {0}, {1}
        		  *    java.lang.String:equalsIgnoreCase(...)@1005: {0}, {1}
        		  *    java.lang.String:equalsIgnoreCase(...)@1013: {0}, {1}
        		  *    java.lang.String:equalsIgnoreCase(...)@997: {1}, {0}
        		  *    java.lang.String:equalsIgnoreCase(...)@997: {0}, {1}
        		  *    java.lang.String:equalsIgnoreCase(...)@999: {1}, {0}
        		  *    ...
        		  */
   985  		callDataIn(line);
   986  
   987  		final String[] token = tokeniseLine(line);
   988  		int nParam;
   989  		setPingNeeded(false);
   990  //		pingCountDown = pingCountDownLength;
   991  
   992  		if (token.length < 2) {
   993  			return;
   994  		}
   995  		try {
   996  			final String sParam = token[1];
   997  			if (token[0].equalsIgnoreCase("PING") || token[1].equalsIgnoreCase("PING")) {
   998  				sendString("PONG :" + sParam);
   999  			} else if (token[0].equalsIgnoreCase("PONG") || token[1].equalsIgnoreCase("PONG")) {
  1000  				if (!lastPingValue.isEmpty() && lastPingValue.equals(token[token.length-1])) {
  1001  					lastPingValue = "";
  1002  					serverLag = System.currentTimeMillis() - pingTime;
  1003  					callPingSuccess();
  1004  				}
  1005  			} else if (token[0].equalsIgnoreCase("ERROR")) {
  1006  				final StringBuilder errorMessage = new StringBuilder();
  1007  				for (int i = 1; i < token.length; ++i) { errorMessage.append(token[i]); }
  1008  				callServerError(errorMessage.toString());
  1009  			} else {
  1010  				if (got001) {
  1011  					// Freenode sends a random notice in a stupid place, others might do aswell
  1012  					// These shouldn't cause post005 to be fired, so handle them here.
  1013  					if (token[0].equalsIgnoreCase("NOTICE")) {
  1014  						try { myProcessingManager.process("Notice Auth", token); } catch (ProcessorNotFoundException e) { }
  1015  						return;
  1016  					}
  1017  					if (!post005) {
  1018  						try { nParam = Integer.parseInt(token[1]); } catch (NumberFormatException e) { nParam = -1; }
  1019  						if (nParam < 0 || nParam > 5) {
  1020  							callPost005();
  1021  						}
  1022  					}
  1023  					// After 001 we potentially care about everything!
  1024  					try { myProcessingManager.process(sParam, token); }
  1025  					catch (ProcessorNotFoundException e) { }
  1026  				} else {
  1027  					// Before 001 we don't care about much.
  1028  					try { nParam = Integer.parseInt(token[1]); } catch (NumberFormatException e) { nParam = -1; }
  1029  					switch (nParam) {
  1030  						case 1: // 001 - Welcome to IRC
  1031  						case 464: // Password Required
  1032  						case 433: // Nick In Use
  1033  							try { myProcessingManager.process(sParam, token); } catch (ProcessorNotFoundException e) { }
  1034  							break;
  1035  						default: // Unknown - Send to Notice Auth
  1036  							// Some networks send a CTCP during the auth process, handle it
  1037  							if (token.length > 3 && !token[3].isEmpty() && token[3].charAt(0) == (char)1 && token[3].charAt(token[3].length()-1) == (char)1) {
  1038  								try { myProcessingManager.process(sParam, token); } catch (ProcessorNotFoundException e) { }
  1039  								break;
  1040  							}
  1041  							// Some networks may send a NICK message if you nick change before 001
  1042  							// Eat it up so that it isn't treated as a notice auth.
  1043  							if (token[1].equalsIgnoreCase("NICK")) { break; }
  1044  							
  1045  							// Otherwise, send to Notice Auth
  1046  							try { myProcessingManager.process("Notice Auth", token); } catch (ProcessorNotFoundException e) { }
  1047  							break;
  1048  					}
  1049  				}
  1050  			}
  1051  		} catch (Exception e) {
  1052  			final ParserError ei = new ParserError(ParserError.ERROR_FATAL, "Fatal Exception in Parser.", getLastLine());
  1053  			ei.setException(e);
  1054  			callErrorInfo(ei);
  1055  		}
  1056  	}
  1057  
  1058  	/** The IRCStringConverter for this parser */
  1059  	private IRCStringConverter stringConverter = null;
  1060  
  1061  	/**
  1062  	 * Get the IRCStringConverter used by this parser.
  1063  	 *
  1064  	 * @return the IRCStringConverter used by this parser. (will create a default
  1065  	 *         one if none exists already);
  1066  	 */
  1067  	public IRCStringConverter getIRCStringConverter() {
        		 /* 
    P/P 		  *  Method: IRCStringConverter getIRCStringConverter()
        		  * 
        		  *  Preconditions:
        		  *    init'ed(this.stringConverter)
        		  * 
        		  *  Postconditions:
        		  *    return_value == One-of{old this.stringConverter, &amp;new IRCStringConverter(getIRCStringConverter#1)}
        		  *    return_value != null
        		  *    this.stringConverter == return_value
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    new IRCStringConverter(getIRCStringConverter#1).limit == 4
        		  *    new IRCStringConverter(getIRCStringConverter#1).lowercase == &amp;new char[](IRCStringConverter#1)
        		  *    new IRCStringConverter(getIRCStringConverter#1).uppercase == &amp;new char[](IRCStringConverter#2)
        		  *    new char[](IRCStringConverter#1) num objects <= 1
        		  *    new char[](IRCStringConverter#1).length == 127
        		  *    possibly_updated(new char[](IRCStringConverter#1)[...])
        		  *    ...
        		  * 
        		  *  Test Vectors:
        		  *    this.stringConverter: Inverse{null}, Addr_Set{null}
        		  */
  1068  		if (stringConverter == null) {
  1069  			stringConverter = new IRCStringConverter((byte)4);
  1070  		}
  1071  		return stringConverter;
  1072  	}
  1073  
  1074  	/**
  1075  	 * Update the character arrays.
  1076  	 *
  1077  	 * @param limit Number of post-alphabetical characters to convert
  1078  	 *              0 = ascii encoding
  1079  	 *              3 = strict-rfc1459 encoding
  1080  	 *              4 = rfc1459 encoding
  1081  	 */
  1082  	protected void updateCharArrays(final byte limit) {
        		 /* 
    P/P 		  *  Method: void updateCharArrays(byte)
        		  * 
        		  *  Postconditions:
        		  *    this.stringConverter == &amp;new IRCStringConverter(updateCharArrays#1)
        		  *    new IRCStringConverter(updateCharArrays#1) num objects == 1
        		  *    new char[](IRCStringConverter#1) num objects == 1
        		  *    new char[](IRCStringConverter#2) num objects == 1
        		  *    this.stringConverter.limit == One-of{4, limit}
        		  *    this.stringConverter.limit in {0..4}
        		  *    this.stringConverter.lowercase == &amp;new char[](IRCStringConverter#1)
        		  *    this.stringConverter.uppercase == &amp;new char[](IRCStringConverter#2)
        		  *    new char[](IRCStringConverter#1).length == 127
        		  *    new char[](IRCStringConverter#2).length == 127
        		  *    ...
        		  */
  1083  		stringConverter = new IRCStringConverter(limit);
  1084  	}
  1085  
  1086  	/**
  1087  	 * Get the known boolean chanmodes in 005 order.
  1088  	 * Modes are returned in the order that the ircd specifies the modes in 005
  1089  	 * with any newly-found modes (mode being set that wasn't specified in 005)
  1090  	 * being added at the end.
  1091  	 *
  1092  	 * @return All the currently known boolean modes
  1093  	 */
  1094  	public String getBoolChanModes005() {
  1095  		// This code isn't the nicest, as Hashtable's don't lend themselves to being
  1096  		// ordered.
  1097  		// Order isn't really important, and this code only takes 3 lines of we
  1098  		// don't care about it but ordered guarentees that on a specific ircd this
  1099  		// method will ALWAYs return the same value.
        		 /* 
    P/P 		  *  Method: String getBoolChanModes005()
        		  * 
        		  *  Preconditions:
        		  *    this.hChanModesBool != null
        		  * 
        		  *  Presumptions:
        		  *    (int) (java.lang.Math:log(...)@1108/java.lang.Math:log(...)@1108) in {0..232-2}
        		  *    java.lang.Math:log(...)@1108 != +0
        		  *    java.util.Iterator:next(...)@1104 != null
        		  *    java.util.Map:get(...)@1105 != null
        		  *    java.util.Map:keySet(...)@1104 != null
        		  *    ...
        		  * 
        		  *  Postconditions:
        		  *    return_value == &amp;new String(getBoolChanModes005#2)
        		  *    new String(getBoolChanModes005#2) num objects == 1
        		  * 
        		  *  Test Vectors:
        		  *    java.lang.Long:longValue(...)@1105: {-263..0}, {1..264-1}
        		  *    java.util.Iterator:hasNext(...)@1104: {0}, {1}
        		  */
  1100  		final char[] modes = new char[hChanModesBool.size()];
  1101  		long nTemp;
  1102  		double pos;
  1103  
  1104  		for (char cTemp : hChanModesBool.keySet()) {
  1105  			nTemp = hChanModesBool.get(cTemp);
  1106  			// nTemp should never be less than 0
  1107  			if (nTemp > 0) {
  1108  				pos = Math.log(nTemp) / Math.log(2);
  1109  				modes[(int)pos] = cTemp;
  1110  			}
  1111  /*			// Is there an easier way to find out the power of 2 value for a number?
  1112  			// ie 1024 = 10, 512 = 9 ?
  1113  			for (int i = 0; i < modes.length; i++) {
  1114  				if (Math.pow(2, i) == (double) nTemp) {
  1115  					modes[i] = cTemp;
  1116  					break;
  1117  				}
  1118  			}*/
  1119  		}
  1120  		return new String(modes);
  1121  	}
  1122  
  1123  	/**
  1124  	 * Process CHANMODES from 005.
  1125  	 */
  1126  	public void parseChanModes() {
        		 /* 
    P/P 		  *  Method: void parseChanModes()
        		  *    parseChanModes fails for all possible inputs
        		  * 
        		  *  Preconditions:
        		  *    this.h005Info != null
        		  *    this.hChanModesBool != null
        		  *    this.hChanModesOther != null
        		  *    init'ed(this.lastLine)
        		  *    this.myCallbackManager != null
        		  *    this.myCallbackManager.callbackHash != null
        		  *    (soft) this.hPrefixModes != null
        		  *    (soft) this.sNetworkName != null
        		  * 
        		  *  Presumptions:
        		  *    java.lang.String:length(...)@1175 >= 1
        		  *    java.lang.String:length(...)@1182 >= 1
        		  *    java.lang.String:length(...)@1189 >= 1
        		  *    java.util.Map:get(...)@1136 != null
        		  *    java.util.Map:get(...)@1148 != null
        		  * 
        		  *  Postconditions:
        		  *    this.nNextKeyCMBool <= 264-2
        		  * 
        		  *  Test Vectors:
        		  *    java.lang.String:equalsIgnoreCase(...)@1131: {0}, {1}
        		  *    java.lang.String:equalsIgnoreCase(...)@1133: {0}, {1}
        		  *    java.lang.StringBuilder:indexOf(...)@1140: {0..232-1}, {-231..-1}
        		  *    java.util.Map:containsKey(...)@1130: {0}, {1}
        		  *    java.util.Map:containsKey(...)@1140: {1}, {0}
        		  *    java.util.Map:containsKey(...)@1147: {0}, {1}
        		  *    java.util.Map:containsKey(...)@1170: {1}, {0}
        		  *    java.util.Map:containsKey(...)@1178: {1}, {0}
        		  *    java.util.Map:containsKey(...)@1185: {1}, {0}
        		  *    java.util.Map:containsKey(...)@1192: {1}, {0}
        		  */
  1127  		final StringBuilder sDefaultModes = new StringBuilder("b,k,l,");
  1128  		String[] bits = null;
  1129  		String modeStr;
  1130  		if (h005Info.containsKey("USERCHANMODES")) {
  1131  			if (getIRCD(true).equalsIgnoreCase("dancer")) {
  1132  				sDefaultModes.insert(0, "dqeI");
  1133  			} else if (getIRCD(true).equalsIgnoreCase("austirc")) {
  1134  				sDefaultModes.insert(0, "e");
  1135  			}
  1136  			modeStr = h005Info.get("USERCHANMODES");
  1137  			char mode;
  1138  			for (int i = 0; i < modeStr.length(); ++i) {
  1139  				mode = modeStr.charAt(i);
  1140  				if (!hPrefixModes.containsKey(mode) && sDefaultModes.indexOf(Character.toString(mode)) < 0) {
  1141  					sDefaultModes.append(mode);
  1142  				}
  1143  			}
  1144  		} else {
  1145  			sDefaultModes.append("imnpstrc");
  1146  		}
  1147  		if (h005Info.containsKey("CHANMODES")) {
  1148  			modeStr = h005Info.get("CHANMODES");
  1149  		} else {
  1150  			modeStr = sDefaultModes.toString();
  1151  			h005Info.put("CHANMODES", modeStr);
  1152  		}
  1153  		bits = modeStr.split(",", 5);
  1154  		if (bits.length < 4) {
  1155  			modeStr = sDefaultModes.toString();
  1156  			callErrorInfo(new ParserError(ParserError.ERROR_ERROR, "CHANMODES String not valid. Using default string of \"" + modeStr + "\"", getLastLine()));
  1157  			h005Info.put("CHANMODES", modeStr);
  1158  			bits = modeStr.split(",", 5);
  1159  		}
  1160  
  1161  		// resetState
  1162  		hChanModesOther.clear();
  1163  		hChanModesBool.clear();
  1164  		nNextKeyCMBool = 1;
  1165  
  1166  		// List modes.
  1167  		for (int i = 0; i < bits[0].length(); ++i) {
  1168  			final Character cMode = bits[0].charAt(i);
  1169  			callDebugInfo(DEBUG_INFO, "Found List Mode: %c", cMode);
  1170  			if (!hChanModesOther.containsKey(cMode)) { hChanModesOther.put(cMode, MODE_LIST); }
  1171  		}
  1172  
  1173  		// Param for Set and Unset.
  1174  		final Byte nBoth = MODE_SET + MODE_UNSET;
  1175  		for (int i = 0; i < bits[1].length(); ++i) {
  1176  			final Character cMode = bits[1].charAt(i);
  1177  			callDebugInfo(DEBUG_INFO, "Found Set/Unset Mode: %c", cMode);
  1178  			if (!hChanModesOther.containsKey(cMode)) { hChanModesOther.put(cMode, nBoth); }
  1179  		}
  1180  
  1181  		// Param just for Set
  1182  		for (int i = 0; i < bits[2].length(); ++i) {
  1183  			final Character cMode = bits[2].charAt(i);
  1184  			callDebugInfo(DEBUG_INFO, "Found Set Only Mode: %c", cMode);
  1185  			if (!hChanModesOther.containsKey(cMode)) { hChanModesOther.put(cMode, MODE_SET); }
  1186  		}
  1187  
  1188  		// Boolean Mode
  1189  		for (int i = 0; i < bits[3].length(); ++i) {
  1190  			final Character cMode = bits[3].charAt(i);
  1191  			callDebugInfo(DEBUG_INFO, "Found Boolean Mode: %c [%d]", cMode, nNextKeyCMBool);
  1192  			if (!hChanModesBool.containsKey(cMode)) {
  1193  				hChanModesBool.put(cMode, nNextKeyCMBool);
  1194  				nNextKeyCMBool = nNextKeyCMBool * 2;
  1195  			}
  1196  		}
  1197  	}
  1198  
  1199  	/**
  1200  	 * Get the known prefixmodes in priority order.
  1201  	 *
  1202  	 * @return All the currently known usermodes
  1203  	 */
  1204  	public String getPrefixModes() {
        		 /* 
    P/P 		  *  Method: String getPrefixModes()
        		  * 
        		  *  Preconditions:
        		  *    this.h005Info != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  * 
        		  *  Test Vectors:
        		  *    java.util.Map:containsKey(...)@1205: {0}, {1}
        		  */
  1205  		if (h005Info.containsKey("PREFIXSTRING")) {
  1206  			return h005Info.get("PREFIXSTRING");
  1207  		} else {
  1208  			return "";
  1209  		}
  1210  	}
  1211  
  1212  	/**
  1213  	 * Get the known boolean chanmodes in alphabetical order.
  1214  	 * Modes are returned in alphabetic order
  1215  	 *
  1216  	 * @return All the currently known boolean modes
  1217  	 */
  1218  	public String getBoolChanModes() {
        		 /* 
    P/P 		  *  Method: String getBoolChanModes()
        		  * 
        		  *  Preconditions:
        		  *    this.hChanModesBool != null
        		  * 
        		  *  Presumptions:
        		  *    java.util.Iterator:next(...)@1221 != null
        		  *    java.util.Map:keySet(...)@1221 != null
        		  *    java.util.Map:size(...)@1219 >= 1
        		  * 
        		  *  Postconditions:
        		  *    return_value == &amp;new String(getBoolChanModes#2)
        		  *    new String(getBoolChanModes#2) num objects == 1
        		  * 
        		  *  Test Vectors:
        		  *    java.util.Iterator:hasNext(...)@1221: {0}, {1}
        		  */
  1219  		final char[] modes = new char[hChanModesBool.size()];
  1220  		int i = 0;
  1221  		for (char mode : hChanModesBool.keySet()) {
  1222  			modes[i++] = mode;
  1223  		}
  1224  		// Alphabetically sort the array
  1225  		Arrays.sort(modes);
  1226  		return new String(modes);
  1227  	}
  1228  
  1229  	/**
  1230  	 * Get the known List chanmodes.
  1231  	 * Modes are returned in alphabetical order
  1232  	 *
  1233  	 * @return All the currently known List modes
  1234  	 */
  1235  	public String getListChanModes() {
        		 /* 
    P/P 		  *  Method: String getListChanModes()
        		  * 
        		  *  Preconditions:
        		  *    this.hChanModesOther != null
        		  * 
        		  *  Postconditions:
        		  *    return_value != null
        		  */
  1236  		return getOtherModeString(MODE_LIST);
  1237  	}
  1238  
  1239  	/**
  1240  	 * Get the known Set-Only chanmodes.
  1241  	 * Modes are returned in alphabetical order
  1242  	 *
  1243  	 * @return All the currently known Set-Only modes
  1244  	 */
  1245  	public String getSetOnlyChanModes() {
        		 /* 
    P/P 		  *  Method: String getSetOnlyChanModes()
        		  * 
        		  *  Preconditions:
        		  *    this.hChanModesOther != null
        		  * 
        		  *  Postconditions:
        		  *    return_value != null
        		  */
  1246  		return getOtherModeString(MODE_SET);
  1247  	}
  1248  
  1249  	/**
  1250  	 * Get the known Set-Unset chanmodes.
  1251  	 * Modes are returned in alphabetical order
  1252  	 *
  1253  	 * @return All the currently known Set-Unset modes
  1254  	 */
  1255  	public String getSetUnsetChanModes() {
        		 /* 
    P/P 		  *  Method: String getSetUnsetChanModes()
        		  * 
        		  *  Preconditions:
        		  *    this.hChanModesOther != null
        		  * 
        		  *  Postconditions:
        		  *    return_value != null
        		  */
  1256  		return getOtherModeString((byte) (MODE_SET + MODE_UNSET));
  1257  	}
  1258  
  1259  	/**
  1260  	 * Get modes from hChanModesOther that have a specific value.
  1261  	 * Modes are returned in alphabetical order
  1262  	 *
  1263  	 * @param nValue Value mode must have to be included
  1264  	 * @return All the currently known Set-Unset modes
  1265  	 */
  1266  	protected String getOtherModeString(final byte nValue) {
        		 /* 
    P/P 		  *  Method: String getOtherModeString(byte)
        		  * 
        		  *  Preconditions:
        		  *    this.hChanModesOther != null
        		  * 
        		  *  Presumptions:
        		  *    java.util.Iterator:next(...)@1270 != null
        		  *    java.util.Map:get(...)@1271 != null
        		  *    java.util.Map:keySet(...)@1270 != null
        		  *    java.util.Map:size(...)@1267 >= 1
        		  * 
        		  *  Postconditions:
        		  *    return_value != null
        		  * 
        		  *  Test Vectors:
        		  *    java.util.Iterator:hasNext(...)@1270: {0}, {1}
        		  */
  1267  		final char[] modes = new char[hChanModesOther.size()];
  1268  		Byte nTemp;
  1269  		int i = 0;
  1270  		for (char cTemp : hChanModesOther.keySet()) {
  1271  			nTemp = hChanModesOther.get(cTemp);
  1272  			if (nTemp == nValue) { modes[i++] = cTemp; }
  1273  		}
  1274  		// Alphabetically sort the array
  1275  		Arrays.sort(modes);
  1276  		return new String(modes).trim();
  1277  	}
  1278  
  1279  	/**
  1280  	 * Get the known usermodes.
  1281  	 * Modes are returned in the order specified by the ircd.
  1282  	 *
  1283  	 * @return All the currently known usermodes (returns "" if usermodes are unknown)
  1284  	 */
  1285  	public String getUserModeString() {
        		 /* 
    P/P 		  *  Method: String getUserModeString()
        		  * 
        		  *  Preconditions:
        		  *    this.h005Info != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  * 
        		  *  Test Vectors:
        		  *    java.util.Map:containsKey(...)@1286: {0}, {1}
        		  */
  1286  		if (h005Info.containsKey("USERMODES")) {
  1287  			return h005Info.get("USERMODES");
  1288  		} else {
  1289  			return "";
  1290  		}
  1291  	}
  1292  
  1293  	/**
  1294  	 * Process USERMODES from 004.
  1295  	 */
  1296  	protected void parseUserModes() {
        		 /* 
    P/P 		  *  Method: void parseUserModes()
        		  * 
        		  *  Preconditions:
        		  *    this.h005Info != null
        		  *    this.hUserModes != null
        		  *    (soft) this.myCallbackManager != null
        		  *    (soft) this.myCallbackManager.callbackHash != null
        		  * 
        		  *  Presumptions:
        		  *    java.util.Map:get(...)@1300 != null
        		  * 
        		  *  Postconditions:
        		  *    this.nNextKeyUser <= 264-2
        		  * 
        		  *  Test Vectors:
        		  *    java.util.Map:containsKey(...)@1299: {0}, {1}
        		  *    java.util.Map:containsKey(...)@1314: {1}, {0}
        		  */
  1297  		final String sDefaultModes = "nwdoi";
  1298  		String modeStr;
  1299  		if (h005Info.containsKey("USERMODES")) {
  1300  			modeStr = h005Info.get("USERMODES");
  1301  		} else {
  1302  			modeStr = sDefaultModes;
  1303  			h005Info.put("USERMODES", sDefaultModes);
  1304  		}
  1305  
  1306  		// resetState
  1307  		hUserModes.clear();
  1308  		nNextKeyUser = 1;
  1309  
  1310  		// Boolean Mode
  1311  		for (int i = 0; i < modeStr.length(); ++i) {
  1312  			final Character cMode = modeStr.charAt(i);
  1313  			callDebugInfo(DEBUG_INFO, "Found User Mode: %c [%d]", cMode, nNextKeyUser);
  1314  			if (!hUserModes.containsKey(cMode)) {
  1315  				hUserModes.put(cMode, nNextKeyUser);
  1316  				nNextKeyUser = nNextKeyUser * 2;
  1317  			}
  1318  		}
  1319  	}
  1320  
  1321  	/**
  1322  	 * Process CHANTYPES from 005.
  1323  	 */
  1324  	protected void parseChanPrefix() {
        		 /* 
    P/P 		  *  Method: void parseChanPrefix()
        		  * 
        		  *  Preconditions:
        		  *    this.h005Info != null
        		  *    this.hChanPrefix != null
        		  *    (soft) this.myCallbackManager != null
        		  *    (soft) this.myCallbackManager.callbackHash != null
        		  * 
        		  *  Presumptions:
        		  *    java.util.Map:get(...)@1328 != null
        		  * 
        		  *  Test Vectors:
        		  *    java.util.Map:containsKey(...)@1327: {0}, {1}
        		  *    java.util.Map:containsKey(...)@1341: {1}, {0}
        		  */
  1325  		final String sDefaultModes = "#&";
  1326  		String modeStr;
  1327  		if (h005Info.containsKey("CHANTYPES")) {
  1328  			modeStr = h005Info.get("CHANTYPES");
  1329  		} else {
  1330  			modeStr = sDefaultModes;
  1331  			h005Info.put("CHANTYPES", sDefaultModes);
  1332  		}
  1333  
  1334  		// resetState
  1335  		hChanPrefix.clear();
  1336  
  1337  		// Boolean Mode
  1338  		for (int i = 0; i < modeStr.length(); ++i) {
  1339  			final Character cMode = modeStr.charAt(i);
  1340  			callDebugInfo(DEBUG_INFO, "Found Chan Prefix: %c", cMode);
  1341  			if (!hChanPrefix.containsKey(cMode)) { hChanPrefix.put(cMode, true); }
  1342  		}
  1343  	}
  1344  
  1345  	/**
  1346  	 * Process PREFIX from 005.
  1347  	 */
  1348  	public void parsePrefixModes() {
        		 /* 
    P/P 		  *  Method: void parsePrefixModes()
        		  * 
        		  *  Preconditions:
        		  *    this.h005Info != null
        		  *    this.hPrefixMap != null
        		  *    this.hPrefixModes != null
        		  *    init'ed(this.lastLine)
        		  *    this.myCallbackManager != null
        		  *    this.myCallbackManager.callbackHash != null
        		  * 
        		  *  Presumptions:
        		  *    java.util.Map:get(...)@1353 != null
        		  * 
        		  *  Postconditions:
        		  *    this.nNextKeyPrefix <= 264-2
        		  * 
        		  *  Test Vectors:
        		  *    java.lang.String:equals(...)@1357: {0}, {1}
        		  *    java.util.Map:containsKey(...)@1352: {0}, {1}
        		  *    java.util.Map:containsKey(...)@1382: {1}, {0}
        		  */
  1349  		final String sDefaultModes = "(ohv)@%+";
  1350  		String[] bits;
  1351  		String modeStr;
  1352  		if (h005Info.containsKey("PREFIX")) {
  1353  			modeStr = h005Info.get("PREFIX");
  1354  		} else {
  1355  			modeStr = sDefaultModes;
  1356  		}
  1357  		if (modeStr.substring(0, 1).equals("(")) {
  1358  			modeStr = modeStr.substring(1);
  1359  		} else {
  1360  			modeStr = sDefaultModes.substring(1);
  1361  			h005Info.put("PREFIX", sDefaultModes);
  1362  		}
  1363  
  1364  		bits = modeStr.split("\\)", 2);
  1365  		if (bits.length != 2 || bits[0].length() != bits[1].length()) {
  1366  			modeStr = sDefaultModes;
  1367  			callErrorInfo(new ParserError(ParserError.ERROR_ERROR, "PREFIX String not valid. Using default string of \"" + modeStr + "\"", getLastLine()));
  1368  			h005Info.put("PREFIX", modeStr);
  1369  			modeStr = modeStr.substring(1);
  1370  			bits = modeStr.split("\\)", 2);
  1371  		}
  1372  
  1373  		// resetState
  1374  		hPrefixModes.clear();
  1375  		hPrefixMap.clear();
  1376  		nNextKeyPrefix = 1;
  1377  
  1378  		for (int i = bits[0].length() - 1; i > -1; --i) {
  1379  			final Character cMode = bits[0].charAt(i);
  1380  			final Character cPrefix = bits[1].charAt(i);
  1381  			callDebugInfo(DEBUG_INFO, "Found Prefix Mode: %c => %c [%d]", cMode, cPrefix, nNextKeyPrefix);
  1382  			if (!hPrefixModes.containsKey(cMode)) {
  1383  				hPrefixModes.put(cMode, nNextKeyPrefix);
  1384  				hPrefixMap.put(cMode, cPrefix);
  1385  				hPrefixMap.put(cPrefix, cMode);
  1386  				nNextKeyPrefix = nNextKeyPrefix * 2;
  1387  			}
  1388  		}
  1389  
  1390  		h005Info.put("PREFIXSTRING", bits[0]);
  1391  	}
  1392  
  1393  	/**
  1394  	 * Check if server is ready.
  1395  	 *
  1396  	 * @return true if 001 has been recieved, false otherwise.
  1397  	 */
        	 /* 
    P/P 	  *  Method: bool isReady()
        	  * 
        	  *  Preconditions:
        	  *    init'ed(this.got001)
        	  * 
        	  *  Postconditions:
        	  *    return_value == this.got001
        	  *    init'ed(return_value)
        	  */
  1398  	public boolean isReady() { return got001; }
  1399  
  1400  	/**
  1401  	 * Join a Channel.
  1402  	 *
  1403  	 * @param sChannelName Name of channel to join
  1404  	 */
  1405  	public void joinChannel(final String sChannelName) {
        		 /* 
    P/P 		  *  Method: void joinChannel(String)
        		  * 
        		  *  Preconditions:
        		  *    (soft) init'ed(this.stringConverter)
        		  *    (soft) this.cMyself != null
        		  *    (soft) init'ed(this.cMyself.bIsFake)
        		  *    (soft) init'ed(this.cMyself.sNickname)
        		  *    (soft) init'ed(this.currentSocketState)
        		  *    (soft) this.h005Info != null
        		  *    (soft) this.hChanModesOther != null
        		  *    (soft) this.hChanPrefix != null
        		  *    (soft) this.hChannelList != null
        		  *    (soft) this.myCallbackManager != null
        		  *    ...
        		  * 
        		  *  Postconditions:
        		  *    possibly_updated(java.lang.String:substring(...)._tainted)
        		  *    possibly_updated(this.cMyself.myAwayReason)
        		  *    init'ed(this.stringConverter)
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    new char[](IRCStringConverter#1) num objects == new IRCStringConverter(getIRCStringConverter#1) num objects
        		  *    new char[](IRCStringConverter#2) num objects == new IRCStringConverter(getIRCStringConverter#1) num objects
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).limit)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).lowercase)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).uppercase)
        		  *    init'ed(new char[](IRCStringConverter#1).length)
        		  *    ...
        		  */
  1406  		joinChannel(sChannelName, "", true);
  1407  	}
  1408  
  1409  	/**
  1410  	 * Join a Channel.
  1411  	 *
  1412  	 * @param sChannelName Name of channel to join
  1413  	 * @param autoPrefix Automatically prepend the first channel prefix defined
  1414  	 *                   in 005 if sChannelName is an invalid channel.
  1415  	 *                   **This only applies to the first channel if given a list**
  1416  	 */
  1417  	public void joinChannel(final String sChannelName, final boolean autoPrefix) {
        		 /* 
    P/P 		  *  Method: void joinChannel(String, bool)
        		  * 
        		  *  Preconditions:
        		  *    (soft) init'ed(this.stringConverter)
        		  *    (soft) this.cMyself != null
        		  *    (soft) init'ed(this.cMyself.bIsFake)
        		  *    (soft) init'ed(this.cMyself.sNickname)
        		  *    (soft) init'ed(this.currentSocketState)
        		  *    (soft) this.h005Info != null
        		  *    (soft) this.hChanModesOther != null
        		  *    (soft) this.hChanPrefix != null
        		  *    (soft) this.hChannelList != null
        		  *    (soft) this.myCallbackManager != null
        		  *    ...
        		  * 
        		  *  Postconditions:
        		  *    possibly_updated(java.lang.String:substring(...)._tainted)
        		  *    possibly_updated(this.cMyself.myAwayReason)
        		  *    init'ed(this.stringConverter)
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    new char[](IRCStringConverter#1) num objects == new IRCStringConverter(getIRCStringConverter#1) num objects
        		  *    new char[](IRCStringConverter#2) num objects == new IRCStringConverter(getIRCStringConverter#1) num objects
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).limit)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).lowercase)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).uppercase)
        		  *    init'ed(new char[](IRCStringConverter#1).length)
        		  *    ...
        		  */
  1418  		joinChannel(sChannelName, "", autoPrefix);
  1419  	}
  1420  
  1421  	/**
  1422  	 * Join a Channel with a key.
  1423  	 *
  1424  	 * @param sChannelName Name of channel to join
  1425  	 * @param sKey Key to use to try and join the channel
  1426  	 */
  1427  	public void joinChannel(final String sChannelName, final String sKey) {
        		 /* 
    P/P 		  *  Method: void joinChannel(String, String)
        		  * 
        		  *  Preconditions:
        		  *    (soft) init'ed(this.stringConverter)
        		  *    (soft) sKey != null
        		  *    (soft) this.cMyself != null
        		  *    (soft) init'ed(this.cMyself.bIsFake)
        		  *    (soft) init'ed(this.cMyself.sNickname)
        		  *    (soft) init'ed(this.currentSocketState)
        		  *    (soft) this.h005Info != null
        		  *    (soft) this.hChanModesOther != null
        		  *    (soft) this.hChanPrefix != null
        		  *    (soft) this.hChannelList != null
        		  *    ...
        		  * 
        		  *  Postconditions:
        		  *    possibly_updated(java.lang.String:substring(...)._tainted)
        		  *    possibly_updated(this.cMyself.myAwayReason)
        		  *    init'ed(this.stringConverter)
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    new char[](IRCStringConverter#1) num objects == new IRCStringConverter(getIRCStringConverter#1) num objects
        		  *    new char[](IRCStringConverter#2) num objects == new IRCStringConverter(getIRCStringConverter#1) num objects
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).limit)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).lowercase)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).uppercase)
        		  *    init'ed(new char[](IRCStringConverter#1).length)
        		  *    ...
        		  */
  1428  		joinChannel(sChannelName, sKey, true);
  1429  	}
  1430  
  1431  	/**
  1432  	 * Join a Channel with a key.
  1433  	 *
  1434  	 * @param sChannelName Name of channel to join
  1435  	 * @param sKey Key to use to try and join the channel
  1436  	 * @param autoPrefix Automatically prepend the first channel prefix defined
  1437  	 *                   in 005 if sChannelName is an invalid channel.
  1438  	 *                   **This only applies to the first channel if given a list**
  1439  	 */
  1440  	public void joinChannel(final String sChannelName, final String sKey, final boolean autoPrefix) {
  1441  		final String channelName;
        		 /* 
    P/P 		  *  Method: void joinChannel(String, String, bool)
        		  * 
        		  *  Preconditions:
        		  *    (soft) init'ed(this.stringConverter)
        		  *    (soft) sKey != null
        		  *    (soft) this.cMyself != null
        		  *    (soft) init'ed(this.cMyself.bIsFake)
        		  *    (soft) init'ed(this.cMyself.sNickname)
        		  *    (soft) init'ed(this.currentSocketState)
        		  *    (soft) this.h005Info != null
        		  *    (soft) this.hChanModesOther != null
        		  *    (soft) this.hChanPrefix != null
        		  *    (soft) this.hChannelList != null
        		  *    ...
        		  * 
        		  *  Presumptions:
        		  *    java.util.Map:get(...)@1447 != null
        		  * 
        		  *  Postconditions:
        		  *    possibly_updated(java.lang.String:substring(...)._tainted)
        		  *    possibly_updated(this.cMyself.myAwayReason)
        		  *    init'ed(this.stringConverter)
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects == 0
        		  *    new char[](IRCStringConverter#1) num objects == 0
        		  *    new char[](IRCStringConverter#2) num objects == 0
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).limit)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).lowercase)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).uppercase)
        		  *    ...
        		  * 
        		  *  Test Vectors:
        		  *    autoPrefix: {0}, {1}
        		  *    java.lang.String:isEmpty(...)@1448: {0}, {1}
        		  *    java.lang.String:isEmpty(...)@1460: {0}, {1}
        		  *    java.util.Map:containsKey(...)@1446: {0}, {1}
        		  */
  1442  		if (isValidChannelName(sChannelName)) {
  1443  			channelName = sChannelName;
  1444  		} else {
  1445  			if (autoPrefix) {
  1446  				if (h005Info.containsKey("CHANTYPES")) {
  1447  					final String chantypes = h005Info.get("CHANTYPES");
  1448  					if (chantypes.isEmpty()) {
  1449  						channelName = "#" + sChannelName;
  1450  					} else {
  1451  						channelName = chantypes.charAt(0) + sChannelName;
  1452  					}
  1453  				} else {
  1454  					return;
  1455  				}
  1456  			} else {
  1457  				return;
  1458  			}
  1459  		}
  1460  		if (sKey.isEmpty()) {
  1461  			sendString("JOIN " + channelName);
  1462  		} else {
  1463  			sendString("JOIN " + channelName + " " + sKey);
  1464  		}
  1465  	}
  1466  
  1467  	/**
  1468  	 * Leave a Channel.
  1469  	 *
  1470  	 * @param sChannelName Name of channel to part
  1471  	 * @param sReason Reason for leaving (Nothing sent if sReason is "")
  1472  	 */
  1473  	public void partChannel(final String sChannelName, final String sReason) {
        		 /* 
    P/P 		  *  Method: void partChannel(String, String)
        		  * 
        		  *  Preconditions:
        		  *    init'ed(this.stringConverter)
        		  *    sChannelName != null
        		  *    this.hChannelList != null
        		  *    (soft) sReason != null
        		  *    (soft) this.cMyself != null
        		  *    (soft) init'ed(this.currentSocketState)
        		  *    (soft) this.hChanModesOther != null
        		  *    (soft) this.myCallbackManager != null
        		  *    (soft) this.myCallbackManager.callbackHash != null
        		  *    (soft) init'ed(this.out)
        		  *    ...
        		  * 
        		  *  Postconditions:
        		  *    init'ed(java.lang.String:substring(...)._tainted)
        		  *    possibly_updated(this.cMyself.myAwayReason)
        		  *    this.stringConverter == One-of{old this.stringConverter, &amp;new IRCStringConverter(getIRCStringConverter#1)}
        		  *    this.stringConverter != null
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    new char[](IRCStringConverter#1) num objects == new IRCStringConverter(getIRCStringConverter#1) num objects
        		  *    new char[](IRCStringConverter#2) num objects == new IRCStringConverter(getIRCStringConverter#1) num objects
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects == 0
        		  *    new IRCStringConverter(getIRCStringConverter#1).limit == 4
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).limit)
        		  *    ...
        		  * 
        		  *  Test Vectors:
        		  *    java.lang.String:isEmpty(...)@1475: {0}, {1}
        		  */
  1474  		if (getChannelInfo(sChannelName) == null) { return; }
  1475  		if (sReason.isEmpty()) {
  1476  			sendString("PART " + sChannelName);
  1477  		} else {
  1478  			sendString("PART " + sChannelName + " :" + sReason);
  1479  		}
  1480  	}
  1481  
  1482  	/**
  1483  	 * Set Nickname.
  1484  	 *
  1485  	 * @param sNewNickName New nickname wanted.
  1486  	 */
  1487  	public void setNickname(final String sNewNickName) {
        		 /* 
    P/P 		  *  Method: void setNickname(String)
        		  * 
        		  *  Preconditions:
        		  *    init'ed(this.currentSocketState)
        		  *    (soft) init'ed(this.stringConverter)
        		  *    (soft) this.cMyself != null
        		  *    (soft) init'ed(this.cMyself.bIsFake)
        		  *    (soft) this.cMyself.sNickname != null
        		  *    (soft) this.hChanModesOther != null
        		  *    (soft) this.hChannelList != null
        		  *    (soft) this.me != null
        		  *    (soft) this.myCallbackManager != null
        		  *    (soft) this.myCallbackManager.callbackHash != null
        		  *    ...
        		  * 
        		  *  Postconditions:
        		  *    init'ed(java.lang.String:substring(...)._tainted)
        		  *    possibly_updated(this.cMyself.myAwayReason)
        		  *    this.me.nickname == One-of{old this.me.nickname, sNewNickName}
        		  *    this.sThinkNickname == One-of{old this.sThinkNickname, sNewNickName}
        		  *    this.stringConverter == One-of{old this.stringConverter, &amp;new IRCStringConverter(getIRCStringConverter#1)}
        		  *    init'ed(this.stringConverter)
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).limit)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).lowercase)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).uppercase)
        		  *    ...
        		  * 
        		  *  Test Vectors:
        		  *    this.cMyself.bIsFake: {1}, {0}
        		  *    java.lang.String:equals(...)@1489: {0}, {1}
        		  */
  1488  		if (getSocketState() == SocketState.OPEN) {
  1489  			if (!cMyself.isFake() && cMyself.getNickname().equals(sNewNickName)) {
  1490  				return;
  1491  			}
  1492  			sendString("NICK " + sNewNickName);
  1493  		} else {
  1494  			me.setNickname(sNewNickName);
  1495  		}
  1496  		sThinkNickname = sNewNickName;
  1497  	}
  1498  
  1499  	/**
  1500  	 * Get the max length a message can be.
  1501  	 *
  1502  	 * @param sType Type of message (ie PRIVMSG)
  1503  	 * @param sTarget Target for message (eg #DMDirc)
  1504  	 * @return Max Length message should be.
  1505  	 */
  1506  	public int getMaxLength(final String sType, final String sTarget) {
  1507  		// If my host is "nick!user@host" and we are sending "#Channel"
  1508  		// a "PRIVMSG" this will find the length of ":nick!user@host PRIVMSG #channel :"
  1509  		// and subtract it from the MAX_LINELENGTH. This should be sufficient in most cases.
  1510  		// Lint = the 2 ":" at the start and end and the 3 separating " "s
        		 /* 
    P/P 		  *  Method: int getMaxLength(String, String)
        		  * 
        		  *  Preconditions:
        		  *    this.cMyself != null
        		  *    init'ed(this.cMyself.bIsFake)
        		  *    (soft) init'ed(this.cMyself.sHost)
        		  *    (soft) init'ed(this.cMyself.sIdent)
        		  *    (soft) init'ed(this.cMyself.sNickname)
        		  *    (soft) init'ed(this.lastLine)
        		  *    (soft) this.myCallbackManager != null
        		  *    (soft) this.myCallbackManager.callbackHash != null
        		  * 
        		  *  Presumptions:
        		  *    java.lang.String:length(...)@1512 <= 2_147_484_153
        		  *    java.lang.String:length(...)@1513 <= 2_147_484_153
        		  *    java.lang.String:length(...)@1513 + length in {0..2_147_484_153}
        		  * 
        		  *  Postconditions:
        		  *    return_value <= 2_147_484_153
        		  * 
        		  *  Test Vectors:
        		  *    sTarget: Addr_Set{null}, Inverse{null}
        		  *    sType: Addr_Set{null}, Inverse{null}
        		  */
  1511  		int length = 0;
  1512  		if (sType != null) { length = length + sType.length(); }
  1513  		if (sTarget != null) { length = length + sTarget.length(); }
  1514  		return getMaxLength(length);
  1515  	}
  1516  
  1517  	/**
  1518  	 * Get the max length a message can be.
  1519  	 *
  1520  	 * @param nLength Length of stuff. (Ie "PRIVMSG"+"#Channel")
  1521  	 * @return Max Length message should be.
  1522  	 */
  1523  	public int getMaxLength(final int nLength) {
        		 /* 
    P/P 		  *  Method: int getMaxLength(int)
        		  * 
        		  *  Preconditions:
        		  *    nLength <= 2_147_484_153
        		  *    this.cMyself != null
        		  *    init'ed(this.cMyself.bIsFake)
        		  *    (soft) init'ed(this.cMyself.sHost)
        		  *    (soft) init'ed(this.cMyself.sIdent)
        		  *    (soft) init'ed(this.cMyself.sNickname)
        		  *    (soft) init'ed(this.lastLine)
        		  *    (soft) this.myCallbackManager != null
        		  *    (soft) this.myCallbackManager.callbackHash != null
        		  * 
        		  *  Presumptions:
        		  *    nLength + java.lang.String:length(...)@1529 in {-231..2_147_484_153}
        		  *    toString(...)@1529 init'ed
        		  * 
        		  *  Postconditions:
        		  *    return_value <= 2_147_484_153
        		  * 
        		  *  Test Vectors:
        		  *    this.cMyself.bIsFake: {0}, {1}
        		  */
  1524  		final int lineLint = 5;
  1525  		if (cMyself.isFake()) {
  1526  			callErrorInfo(new ParserError(ParserError.ERROR_ERROR + ParserError.ERROR_USER, "getMaxLength() called, but I don't know who I am?", getLastLine()));
  1527  			return MAX_LINELENGTH - nLength - lineLint;
  1528  		} else {
  1529  			return MAX_LINELENGTH - cMyself.toString().length() - nLength - lineLint;
  1530  		}
  1531  	}
  1532  
  1533  	/**
  1534  	 * Get the max number of list modes.
  1535  	 *
  1536  	 * @param mode The mode to know the max number for
  1537  	 * @return The max number of list modes for the given mode.
  1538  	 *         - returns 0 if MAXLIST does not contain the mode, unless MAXBANS is
  1539  	 *           set, then this is returned instead.
  1540  	 *         - returns -1 if:
  1541  	 *           - MAXLIST or MAXBANS were not in 005
  1542  	 *           - Values for MAXLIST or MAXBANS were invalid (non integer, empty)
  1543  	 */
  1544  	public int getMaxListModes(final char mode) {
  1545  		// MAXLIST=bdeI:50
  1546  		// MAXLIST=b:60,e:60,I:60
  1547  		// MAXBANS=30
        		 /* 
    P/P 		  *  Method: int getMaxListModes(char)
        		  * 
        		  *  Preconditions:
        		  *    this.h005Info != null
        		  *    this.myCallbackManager != null
        		  *    this.myCallbackManager.callbackHash != null
        		  *    (soft) init'ed(this.lastLine)
        		  *    (soft) this.sNetworkName != null
        		  * 
        		  *  Presumptions:
        		  *    java.util.Map:get(...)@1555 != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  * 
        		  *  Test Vectors:
        		  *    java.lang.String:equalsIgnoreCase(...)@1577: {0}, {1}
        		  *    java.util.Map:get(...)@1551: Addr_Set{null}, Inverse{null}
        		  *    java.util.Map:get(...)@1552: Inverse{null}, Addr_Set{null}
        		  *    java.util.Map:get(...)@1572: Addr_Set{null}, Inverse{null}
        		  */
  1548  		int result = -2;
  1549  		callDebugInfo(DEBUG_INFO, "Looking for maxlistmodes for: "+mode);
  1550  		// Try in MAXLIST
  1551  		if (h005Info.get("MAXLIST") != null) {
  1552  			if (h005Info.get("MAXBANS") == null) {
  1553  				result = 0;
  1554  			}
  1555  			final String maxlist = h005Info.get("MAXLIST");
  1556  			callDebugInfo(DEBUG_INFO, "Found maxlist ("+maxlist+")");
  1557  			final String[] bits = maxlist.split(",");
  1558  			for (String bit : bits) {
  1559  				final String[] parts = bit.split(":", 2);
  1560  				callDebugInfo(DEBUG_INFO, "Bit: "+bit+" | parts.length = "+parts.length+" ("+parts[0]+" -> "+parts[0].indexOf(mode)+")");
  1561  				if (parts.length == 2 && parts[0].indexOf(mode) > -1) {
  1562  					callDebugInfo(DEBUG_INFO, "parts[0] = '"+parts[0]+"' | parts[1] = '"+parts[1]+"'");
  1563  					try {
  1564  						result = Integer.parseInt(parts[1]);
  1565  						break;
  1566  					} catch (NumberFormatException nfe) { result = -1; }
  1567  				}
  1568  			}
  1569  		}
  1570  
  1571  		// If not in max list, try MAXBANS
  1572  		if (result == -2 && h005Info.get("MAXBANS") != null) {
  1573  			callDebugInfo(DEBUG_INFO, "Trying max bans");
  1574  			try {
  1575  				result = Integer.parseInt(h005Info.get("MAXBANS"));
  1576  			} catch (NumberFormatException nfe) { result = -1; }
  1577  		} else if (result == -2 && getIRCD(true).equalsIgnoreCase("weircd")) {
  1578  			// -_-
  1579  			result = 50;
  1580  		} else if (result == -2) {
  1581  			result = -1;
  1582  			callDebugInfo(DEBUG_INFO, "Failed");
  1583  			callErrorInfo(new ParserError(ParserError.ERROR_ERROR, "Unable to discover max list modes.", getLastLine()));
  1584  		}
  1585  		callDebugInfo(DEBUG_INFO, "Result: "+result);
  1586  		return result;
  1587  	}
  1588  
  1589  	/**
  1590  	 * Send a private message to a target.
  1591  	 *
  1592  	 * @param sTarget Target
  1593  	 * @param sMessage Message to send
  1594  	 */
  1595  	public void sendMessage(final String sTarget, final String sMessage) {
        		 /* 
    P/P 		  *  Method: void sendMessage(String, String)
        		  * 
        		  *  Preconditions:
        		  *    (soft) init'ed(this.stringConverter)
        		  *    (soft) this.cMyself != null
        		  *    (soft) init'ed(this.currentSocketState)
        		  *    (soft) this.hChanModesOther != null
        		  *    (soft) this.hChannelList != null
        		  *    (soft) this.myCallbackManager != null
        		  *    (soft) this.myCallbackManager.callbackHash != null
        		  *    (soft) init'ed(this.out)
        		  * 
        		  *  Postconditions:
        		  *    init'ed(java.lang.String:substring(...)._tainted)
        		  *    possibly_updated(this.cMyself.myAwayReason)
        		  *    this.stringConverter == One-of{old this.stringConverter, &amp;new IRCStringConverter(getIRCStringConverter#1)}
        		  *    init'ed(this.stringConverter)
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).limit)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).lowercase)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).uppercase)
        		  *    new char[](IRCStringConverter#1) num objects <= 1
        		  *    init'ed(new char[](IRCStringConverter#1).length)
        		  *    ...
        		  * 
        		  *  Test Vectors:
        		  *    sMessage: Inverse{null}, Addr_Set{null}
        		  *    sTarget: Addr_Set{null}, Inverse{null}
        		  *    java.lang.String:isEmpty(...)@1597: {0}, {1}
        		  */
  1596  		if (sTarget == null || sMessage == null) { return; }
  1597  		if (sTarget.isEmpty()/* || sMessage.isEmpty()*/) { return; }
  1598  
  1599  		sendString("PRIVMSG " + sTarget + " :" + sMessage);
  1600  	}
  1601  
  1602  	/**
  1603  	 * Send a notice message to a target.
  1604  	 *
  1605  	 * @param sTarget Target
  1606  	 * @param sMessage Message to send
  1607  	 */
  1608  	public void sendNotice(final String sTarget, final String sMessage) {
        		 /* 
    P/P 		  *  Method: void sendNotice(String, String)
        		  * 
        		  *  Preconditions:
        		  *    (soft) init'ed(this.stringConverter)
        		  *    (soft) this.cMyself != null
        		  *    (soft) init'ed(this.currentSocketState)
        		  *    (soft) this.hChanModesOther != null
        		  *    (soft) this.hChannelList != null
        		  *    (soft) this.myCallbackManager != null
        		  *    (soft) this.myCallbackManager.callbackHash != null
        		  *    (soft) init'ed(this.out)
        		  * 
        		  *  Postconditions:
        		  *    init'ed(java.lang.String:substring(...)._tainted)
        		  *    possibly_updated(this.cMyself.myAwayReason)
        		  *    this.stringConverter == One-of{old this.stringConverter, &amp;new IRCStringConverter(getIRCStringConverter#1)}
        		  *    init'ed(this.stringConverter)
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).limit)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).lowercase)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).uppercase)
        		  *    new char[](IRCStringConverter#1) num objects <= 1
        		  *    init'ed(new char[](IRCStringConverter#1).length)
        		  *    ...
        		  * 
        		  *  Test Vectors:
        		  *    sMessage: Inverse{null}, Addr_Set{null}
        		  *    sTarget: Addr_Set{null}, Inverse{null}
        		  *    java.lang.String:isEmpty(...)@1610: {0}, {1}
        		  */
  1609  		if (sTarget == null || sMessage == null) { return; }
  1610  		if (sTarget.isEmpty()/* || sMessage.isEmpty()*/) { return; }
  1611  
  1612  		sendString("NOTICE " + sTarget + " :" + sMessage);
  1613  	}
  1614  
  1615  	/**
  1616  	 * Send a Action to a target.
  1617  	 *
  1618  	 * @param sTarget Target
  1619  	 * @param sMessage Action to send
  1620  	 */
  1621  	public void sendAction(final String sTarget, final String sMessage) {
        		 /* 
    P/P 		  *  Method: void sendAction(String, String)
        		  * 
        		  *  Preconditions:
        		  *    (soft) init'ed(this.stringConverter)
        		  *    (soft) this.cMyself != null
        		  *    (soft) init'ed(this.currentSocketState)
        		  *    (soft) this.hChanModesOther != null
        		  *    (soft) this.hChannelList != null
        		  *    (soft) this.myCallbackManager != null
        		  *    (soft) this.myCallbackManager.callbackHash != null
        		  *    (soft) init'ed(this.out)
        		  * 
        		  *  Postconditions:
        		  *    init'ed(java.lang.String:substring(...)._tainted)
        		  *    possibly_updated(this.cMyself.myAwayReason)
        		  *    this.stringConverter == One-of{old this.stringConverter, &amp;new IRCStringConverter(getIRCStringConverter#1)}
        		  *    init'ed(this.stringConverter)
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    new char[](IRCStringConverter#1) num objects == new IRCStringConverter(getIRCStringConverter#1) num objects
        		  *    new char[](IRCStringConverter#2) num objects == new IRCStringConverter(getIRCStringConverter#1) num objects
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).limit)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).lowercase)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).uppercase)
        		  *    ...
        		  */
  1622  		sendCTCP(sTarget, "ACTION", sMessage);
  1623  	}
  1624  
  1625  	/**
  1626  	 * Send a CTCP to a target.
  1627  	 *
  1628  	 * @param sTarget Target
  1629  	 * @param sType Type of CTCP
  1630  	 * @param sMessage Optional Additional Parameters
  1631  	 */
  1632  	public void sendCTCP(final String sTarget, final String sType, final String sMessage) {
        		 /* 
    P/P 		  *  Method: void sendCTCP(String, String, String)
        		  * 
        		  *  Preconditions:
        		  *    (soft) init'ed(this.stringConverter)
        		  *    (soft) sType != null
        		  *    (soft) this.cMyself != null
        		  *    (soft) init'ed(this.currentSocketState)
        		  *    (soft) this.hChanModesOther != null
        		  *    (soft) this.hChannelList != null
        		  *    (soft) this.myCallbackManager != null
        		  *    (soft) this.myCallbackManager.callbackHash != null
        		  *    (soft) init'ed(this.out)
        		  * 
        		  *  Postconditions:
        		  *    init'ed(java.lang.String:substring(...)._tainted)
        		  *    possibly_updated(this.cMyself.myAwayReason)
        		  *    this.stringConverter == One-of{old this.stringConverter, &amp;new IRCStringConverter(getIRCStringConverter#1)}
        		  *    init'ed(this.stringConverter)
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).limit)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).lowercase)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).uppercase)
        		  *    new char[](IRCStringConverter#1) num objects <= 1
        		  *    init'ed(new char[](IRCStringConverter#1).length)
        		  *    ...
        		  * 
        		  *  Test Vectors:
        		  *    sMessage: Inverse{null}, Addr_Set{null}
        		  *    sTarget: Addr_Set{null}, Inverse{null}
        		  *    java.lang.String:isEmpty(...)@1634: {1}, {0}
        		  *    java.lang.String:isEmpty(...)@1634: {0}, {1}
        		  */
  1633  		if (sTarget == null || sMessage == null) { return; }
  1634  		if (sTarget.isEmpty() || sType.isEmpty()) { return; }
  1635  		final char char1 = (char) 1;
  1636  		sendString("PRIVMSG " + sTarget + " :" + char1 + sType.toUpperCase() + " " + sMessage + char1);
  1637  	}
  1638  
  1639  	/**
  1640  	 * Send a CTCPReply to a target.
  1641  	 *
  1642  	 * @param sTarget Target
  1643  	 * @param sType Type of CTCP
  1644  	 * @param sMessage Optional Additional Parameters
  1645  	 */
  1646  	public void sendCTCPReply(final String sTarget, final String sType, final String sMessage) {
        		 /* 
    P/P 		  *  Method: void sendCTCPReply(String, String, String)
        		  * 
        		  *  Preconditions:
        		  *    (soft) init'ed(this.stringConverter)
        		  *    (soft) sType != null
        		  *    (soft) this.cMyself != null
        		  *    (soft) init'ed(this.currentSocketState)
        		  *    (soft) this.hChanModesOther != null
        		  *    (soft) this.hChannelList != null
        		  *    (soft) this.myCallbackManager != null
        		  *    (soft) this.myCallbackManager.callbackHash != null
        		  *    (soft) init'ed(this.out)
        		  * 
        		  *  Postconditions:
        		  *    init'ed(java.lang.String:substring(...)._tainted)
        		  *    possibly_updated(this.cMyself.myAwayReason)
        		  *    this.stringConverter == One-of{old this.stringConverter, &amp;new IRCStringConverter(getIRCStringConverter#1)}
        		  *    init'ed(this.stringConverter)
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).limit)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).lowercase)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).uppercase)
        		  *    new char[](IRCStringConverter#1) num objects <= 1
        		  *    init'ed(new char[](IRCStringConverter#1).length)
        		  *    ...
        		  * 
        		  *  Test Vectors:
        		  *    sMessage: Inverse{null}, Addr_Set{null}
        		  *    sTarget: Addr_Set{null}, Inverse{null}
        		  *    java.lang.String:isEmpty(...)@1648: {1}, {0}
        		  *    java.lang.String:isEmpty(...)@1648: {0}, {1}
        		  */
  1647  		if (sTarget == null || sMessage == null) { return; }
  1648  		if (sTarget.isEmpty() || sType.isEmpty()) { return; }
  1649  		final char char1 = (char) 1;
  1650  		sendString("NOTICE " + sTarget + " :" + char1 + sType.toUpperCase() + " " + sMessage + char1);
  1651  	}
  1652  
  1653  	/**
  1654  	 * Quit IRC.
  1655  	 * This method will wait for the server to close the socket.
  1656  	 *
  1657  	 * @param sReason Reason for quitting.
  1658  	 */
  1659  	public void quit(final String sReason) {
        		 /* 
    P/P 		  *  Method: void quit(String)
        		  * 
        		  *  Preconditions:
        		  *    sReason != null
        		  *    init'ed(this.out)
        		  *    (soft) init'ed(this.stringConverter)
        		  *    (soft) this.cMyself != null
        		  *    (soft) init'ed(this.currentSocketState)
        		  *    (soft) this.hChanModesOther != null
        		  *    (soft) this.hChannelList != null
        		  *    (soft) this.myCallbackManager != null
        		  *    (soft) this.myCallbackManager.callbackHash != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(java.lang.String:substring(...)._tainted)
        		  *    possibly_updated(this.cMyself.myAwayReason)
        		  *    this.stringConverter == One-of{old this.stringConverter, &amp;new IRCStringConverter(getIRCStringConverter#1)}
        		  *    init'ed(this.stringConverter)
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).limit)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).lowercase)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).uppercase)
        		  *    new char[](IRCStringConverter#1) num objects <= 1
        		  *    init'ed(new char[](IRCStringConverter#1).length)
        		  *    ...
        		  * 
        		  *  Test Vectors:
        		  *    java.lang.String:isEmpty(...)@1660: {0}, {1}
        		  */
  1660  		if (sReason.isEmpty()) {
  1661  			sendString("QUIT");
  1662  		} else {
  1663  			sendString("QUIT :" + sReason);
  1664  		}
  1665  	}
  1666  	/**
  1667  	 * Disconnect from server.
  1668  	 * This method will quit and automatically close the socket without waiting for
  1669  	 * the server.
  1670  	 *
  1671  	 * @param sReason Reason for quitting.
  1672  	 */
  1673  	public void disconnect(final String sReason) {
        		 /* 
    P/P 		  *  Method: void disconnect(String)
        		  * 
        		  *  Preconditions:
        		  *    init'ed(this.currentSocketState)
        		  *    init'ed(this.pingTimer)
        		  *    this.h005Info != null
        		  *    this.hChanModesBool != null
        		  *    this.hChanModesOther != null
        		  *    this.hChanPrefix != null
        		  *    this.hChannelList != null
        		  *    this.hClientList != null
        		  *    this.hPrefixMap != null
        		  *    this.hPrefixModes != null
        		  *    ...
        		  * 
        		  *  Postconditions:
        		  *    init'ed(java.lang.String:substring(...)._tainted)
        		  *    this.cMyself in Addr_Set{&amp;new ClientInfo(resetState#1),&amp;new ClientInfo(resetState#1)}
        		  *    possibly_updated(this.cMyself.myAwayReason)
        		  *    this.currentSocketState == &amp;com.dmdirc.parser.irc.SocketState__static_init.new SocketState(SocketState__static_init#2)
        		  *    this.got001 == 0
        		  *    this.lastLine == &amp;""
        		  *    this.nNextKeyCMBool == 1
        		  *    this.nNextKeyPrefix == 1
        		  *    this.nNextKeyUser == 1
        		  *    this.pingTimer == null
        		  *    ...
        		  * 
        		  *  Test Vectors:
        		  *    this.got001: {0}, {1}
        		  */
  1674  		if (currentSocketState == SocketState.OPEN && got001) { quit(sReason); }
  1675  		try {
  1676  			if (socket != null) { socket.close(); }
  1677  		} catch (IOException e) {
  1678  			/* Do Nothing */
  1679  		} finally {
  1680  			if (currentSocketState != SocketState.CLOSED) {
  1681  				currentSocketState = SocketState.CLOSED;
  1682  				callSocketClosed();
  1683  			}
  1684  			resetState();
  1685  		}
  1686  	}
  1687  
  1688  	/**
  1689  	 * Check if a channel name is valid.
  1690  	 *
  1691  	 * @param sChannelName Channel name to test
  1692  	 * @return true if name is valid on the current connection, false otherwise.
  1693  	 *         - Before channel prefixes are known (005/noMOTD/MOTDEnd), this checks
  1694  	 *           that the first character is either #, &amp;, ! or +
  1695  	 *         - Assumes that any channel that is already known is valid, even if
  1696  	 *           005 disagrees.
  1697  	 */
  1698  	public boolean isValidChannelName(final String sChannelName) {
  1699  		// Check sChannelName is not empty or null
        		 /* 
    P/P 		  *  Method: bool isValidChannelName(String)
        		  * 
        		  *  Preconditions:
        		  *    (soft) init'ed(this.stringConverter)
        		  *    (soft) this.cMyself != null
        		  *    (soft) init'ed(this.cMyself.bIsFake)
        		  *    (soft) init'ed(this.cMyself.sNickname)
        		  *    (soft) this.hChanPrefix != null
        		  *    (soft) this.hChannelList != null
        		  *    (soft) init'ed(this.sThinkNickname)
        		  * 
        		  *  Presumptions:
        		  *    getIRCStringConverter(...).lowercase.length@1702 >= 1
        		  *    getIRCStringConverter(...).lowercase@1702 != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  *    init'ed(this.stringConverter)
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects == 0
        		  *    new IRCStringConverter(getIRCStringConverter#1).limit == 4
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).limit)
        		  *    new IRCStringConverter(getIRCStringConverter#1).lowercase == &amp;new char[](IRCStringConverter#1)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).lowercase)
        		  *    new IRCStringConverter(getIRCStringConverter#1).uppercase == &amp;new char[](IRCStringConverter#2)
        		  *    init'ed(new IRCStringConverter(getIRCStringConverter#1).uppercase)
        		  *    ...
        		  * 
        		  *  Test Vectors:
        		  *    sChannelName: Addr_Set{null}, Inverse{null}
        		  *    java.lang.String:isEmpty(...)@1700: {0}, {1}
        		  *    java.util.Map:isEmpty(...)@1706: {0}, {1}
        		  */
  1700  		if (sChannelName == null || sChannelName.isEmpty()) { return false; }
  1701  		// Check its not ourself (PM recieved before 005)
  1702  		if (getIRCStringConverter().equalsIgnoreCase(getMyNickname(), sChannelName)) { return false; }
  1703  		// Check if we are already on this channel
  1704  		if (getChannelInfo(sChannelName) != null) { return true; }
  1705  		// Check if we know of any valid chan prefixes
  1706  		if (hChanPrefix.isEmpty()) {
  1707  			// We don't. Lets check against RFC2811-Specified channel types
  1708  			final char first = sChannelName.charAt(0);
  1709  			return first == '#' || first == '&' || first == '!' || first == '+';
  1710  		}
  1711  		// Otherwise return true if:
  1712  		// Channel equals "0"
  1713  		// first character of the channel name is a valid channel prefix.
  1714  		return hChanPrefix.containsKey(sChannelName.charAt(0)) || sChannelName.equals("0");
  1715  	}
  1716  
  1717  	/**
  1718  	 * Check if a given chanmode is user settable.
  1719  	 *
  1720  	 * @param mode Mode to test
  1721  	 * @return true if mode is settable by users, false if servers only
  1722  	 */
  1723  	public boolean isUserSettable(final Character mode) {
  1724  		String validmodes;
        		 /* 
    P/P 		  *  Method: bool isUserSettable(Character)
        		  * 
        		  *  Preconditions:
        		  *    this.h005Info != null
        		  * 
        		  *  Presumptions:
        		  *    java.util.Map:get(...)@1726 != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  * 
        		  *  Test Vectors:
        		  *    java.util.Map:containsKey(...)@1725: {0}, {1}
        		  */
  1725  		if (h005Info.containsKey("USERCHANMODES")) {
  1726  			validmodes = h005Info.get("USERCHANMODES");
  1727  		} else {
  1728  			validmodes = "bklimnpstrc";
  1729  		}
  1730  		return validmodes.matches(".*" + mode + ".*");
  1731  	}
  1732  
  1733  	/**
  1734  	 * Get the 005 info.
  1735  	 *
  1736  	 * @return 005Info hashtable.
  1737  	 */
        	 /* 
    P/P 	  *  Method: Map get005()
        	  * 
        	  *  Postconditions:
        	  *    return_value == this.h005Info
        	  *    init'ed(return_value)
        	  */
  1738  	public Map<String, String> get005() { return h005Info; }
  1739  
  1740  	/**
  1741  	 * Get the name of the ircd.
  1742  	 *
  1743  	 * @param getType if this is false the string from 004 is returned. Else a guess of the type (ircu, hybrid, ircnet)
  1744  	 * @return IRCD Version or Type
  1745  	 */
  1746  	public String getIRCD(final boolean getType) {
        		 /* 
    P/P 		  *  Method: String getIRCD(bool)
        		  * 
        		  *  Preconditions:
        		  *    this.h005Info != null
        		  *    (soft) this.sNetworkName != null
        		  * 
        		  *  Presumptions:
        		  *    java.util.Map:get(...)@1748 != null
        		  *    java.util.Map:get(...)@1784 != null
        		  * 
        		  *  Postconditions:
        		  *    return_value != null
        		  * 
        		  *  Test Vectors:
        		  *    getType: {0}, {1}
        		  *    java.lang.String:equalsIgnoreCase(...)@1781: {0}, {1}
        		  *    java.lang.String:equalsIgnoreCase(...)@1782: {0}, {1}
        		  *    java.lang.String:equalsIgnoreCase(...)@1783: {0}, {1}
        		  *    java.lang.String:matches(...)@1752: {0}, {1}
        		  *    java.lang.String:matches(...)@1753: {0}, {1}
        		  *    java.lang.String:matches(...)@1754: {0}, {1}
        		  *    java.lang.String:matches(...)@1755: {0}, {1}
        		  *    java.lang.String:matches(...)@1756: {0}, {1}
        		  *    java.lang.String:matches(...)@1757: {0}, {1}
        		  *    ...
        		  */
  1747  		if (h005Info.containsKey("004IRCD")) {
  1748  			final String version = h005Info.get("004IRCD");
  1749  			if (getType) {
  1750  				// This ilst is vaugly based on http://searchirc.com/ircd-versions,
  1751  				// but keeping groups of ircd's together (ie hybrid-based, ircu-based)
  1752  				if (version.matches("(?i).*unreal.*")) { return "unreal"; }
  1753  				else if (version.matches("(?i).*bahamut.*")) { return "bahamut"; }
  1754  				else if (version.matches("(?i).*nefarious.*")) { return "nefarious"; }
  1755  				else if (version.matches("(?i).*asuka.*")) { return "asuka"; }
  1756  				else if (version.matches("(?i).*snircd.*")) { return "snircd"; }
  1757  				else if (version.matches("(?i).*beware.*")) { return "bircd"; }
  1758  				else if (version.matches("(?i).*u2\\.[0-9]+\\.H\\..*")) { return "irchispano"; }
  1759  				else if (version.matches("(?i).*u2\\.[0-9]+\\..*")) { return "ircu"; }
  1760  				else if (version.matches("(?i).*ircu.*")) { return "ircu"; }
  1761  				else if (version.matches("(?i).*plexus.*")) { return "plexus"; }
  1762  				else if (version.matches("(?i).*hybrid.*oftc.*")) { return "oftc-hybrid"; }
  1763  				else if (version.matches("(?i).*ircd.hybrid.*")) { return "hybrid7"; }
  1764  				else if (version.matches("(?i).*hybrid.*")) { return "hybrid"; }
  1765  				else if (version.matches("(?i).*charybdis.*")) { return "charybdis"; }
  1766  				else if (version.matches("(?i).*inspircd.*")) { return "inspircd"; }
  1767  				else if (version.matches("(?i).*ultimateircd.*")) { return "ultimateircd"; }
  1768  				else if (version.matches("(?i).*critenircd.*")) { return "critenircd"; }
  1769  				else if (version.matches("(?i).*fqircd.*")) { return "fqircd"; }
  1770  				else if (version.matches("(?i).*conferenceroom.*")) { return "conferenceroom"; }
  1771  				else if (version.matches("(?i).*hyperion.*")) { return "hyperion"; }
  1772  				else if (version.matches("(?i).*dancer.*")) { return "dancer"; }
  1773  				else if (version.matches("(?i).*austhex.*")) { return "austhex"; }
  1774  				else if (version.matches("(?i).*austirc.*")) { return "austirc"; }
  1775  				else if (version.matches("(?i).*ratbox.*")) { return "ratbox"; }
  1776  				else if (version.matches("(?i).*euircd.*")) { return "euircd"; }
  1777  				else if (version.matches("(?i).*weircd.*")) { return "weircd"; }
  1778  				else if (version.matches("(?i).*swiftirc.*")) { return "swiftirc"; }
  1779  				else {
  1780  					// Stupid networks/ircds go here...
  1781  					if (sNetworkName.equalsIgnoreCase("ircnet")) { return "ircnet"; }
  1782  					else if (sNetworkName.equalsIgnoreCase("starchat")) { return "starchat"; }
  1783  					else if (sNetworkName.equalsIgnoreCase("bitlbee")) { return "bitlbee"; }
  1784  					else if (h005Info.containsKey("003IRCD") && h005Info.get("003IRCD").matches("(?i).*bitlbee.*")) { return "bitlbee"; } // Older bitlbee
  1785  					else { return "generic"; }
  1786  				}
  1787  			} else {
  1788  				return version;
  1789  			}
  1790  		} else {
  1791  			if (getType) { return "generic"; }
  1792  			else { return ""; }
  1793  		}
  1794  	}
  1795  
  1796  
  1797  	/**
  1798  	 * Get the time used for the ping Timer.
  1799  	 *
  1800  	 * @return current time used.
  1801  	 * @see setPingCountDownLength
  1802  	 */
        	 /* 
    P/P 	  *  Method: long getPingTimerLength()
        	  * 
        	  *  Preconditions:
        	  *    init'ed(this.pingTimerLength)
        	  * 
        	  *  Postconditions:
        	  *    return_value == this.pingTimerLength
        	  *    init'ed(return_value)
        	  */
  1803  	public long getPingTimerLength() { return pingTimerLength; }
  1804  
  1805  	/**
  1806  	 * Set the time used for the ping Timer.
  1807  	 * This will also reset the pingTimer.
  1808  	 *
  1809  	 * @param newValue New value to use.
  1810  	 * @see setPingCountDownLength
  1811  	 */
  1812  	public void setPingTimerLength(final long newValue) {
        		 /* 
    P/P 		  *  Method: void setPingTimerLength(long)
        		  * 
        		  *  Preconditions:
        		  *    init'ed(this.pingTimer)
        		  *    this.pingNeeded != null
        		  *    this.pingTimerSem != null
        		  * 
        		  *  Postconditions:
        		  *    this.pingCountDown == 1
        		  *    new Timer(startPingTimer#1) num objects == 1
        		  *    this.pingTimer == &amp;new Timer(startPingTimer#1)
        		  *    this.pingTimerLength == newValue
        		  *    init'ed(this.pingTimerLength)
        		  */
  1813  		pingTimerLength = newValue;
  1814  		startPingTimer();
  1815  	}
  1816  
  1817  	/**
  1818  	 * Get the time used for the pingCountdown.
  1819  	 *
  1820  	 * @return current time used.
  1821  	 * @see setPingCountDownLength
  1822  	 */
        	 /* 
    P/P 	  *  Method: int getPingCountDownLength()
        	  * 
        	  *  Preconditions:
        	  *    init'ed(this.pingCountDownLength)
        	  * 
        	  *  Postconditions:
        	  *    return_value == this.pingCountDownLength
        	  *    init'ed(return_value)
        	  */
  1823  	public int getPingCountDownLength() { return pingCountDownLength; }
  1824  
  1825  	/**
  1826  	 * Set the time used for the ping countdown.
  1827  	 * The pingTimer fires every pingTimerLength/1000 seconds, whenever a line of data
  1828  	 * is received, the "waiting for ping" flag is set to false, if the line is
  1829  	 * a "PONG", then onPingSuccess is also called.
  1830  	 *
  1831  	 * When waiting for a ping reply, onPingFailed() is called every time the
  1832  	 * timer is fired.
  1833  	 *
  1834  	 * When not waiting for a ping reply, the pingCountDown is decreased by 1
  1835  	 * every time the timer fires, when it reaches 0 is is reset to
  1836  	 * pingCountDownLength and a PING is sent to the server.
  1837  	 *
  1838  	 * To ping the server after 30 seconds of inactivity you could use:
  1839  	 * pingTimerLength = 5000, pingCountDown = 6
  1840  	 * or
  1841  	 * pingTimerLength = 10000, pingCountDown = 3
  1842  	 *
  1843  	 * @param newValue New value to use.
  1844  	 * @see pingCountDown
  1845  	 * @see pingTimerLength
  1846  	 * @see pingTimerTask
  1847  	 */
  1848  	public void setPingCountDownLength(final int newValue) {
        		 /* 
    P/P 		  *  Method: void setPingCountDownLength(int)
        		  * 
        		  *  Postconditions:
        		  *    this.pingCountDownLength == newValue
        		  *    init'ed(this.pingCountDownLength)
        		  */
  1849  		pingCountDownLength = newValue;
  1850  	}
  1851  
  1852  	/**
  1853  	 * Start the pingTimer.
  1854  	 */
  1855  	protected void startPingTimer() {
                 /* 
    P/P           *  Method: void startPingTimer()
                  * 
                  *  Preconditions:
                  *    init'ed(this.pingTimer)
                  *    this.pingNeeded != null
                  *    init'ed(this.pingTimerLength)
                  *    this.pingTimerSem != null
                  * 
                  *  Postconditions:
                  *    this.pingCountDown == 1
                  *    new Timer(startPingTimer#1) num objects == 1
                  *    this.pingTimer == &amp;new Timer(startPingTimer#1)
                  * 
                  *  Test Vectors:
                  *    this.pingTimer: Addr_Set{null}, Inverse{null}
                  */
  1856          pingTimerSem.acquireUninterruptibly();
  1857  
  1858  		setPingNeeded(false);
  1859  		if (pingTimer != null) { pingTimer.cancel(); }
  1860  		pingTimer = new Timer("IRCParser pingTimer");
  1861  		pingTimer.schedule(new PingTimer(this, pingTimer), 0, pingTimerLength);
  1862  		pingCountDown = 1;
  1863  
  1864          pingTimerSem.release();
  1865  	}
  1866  
  1867  	/**
  1868  	 * This is called when the ping Timer has been executed.
  1869  	 * As the timer is restarted on every incomming message, this will only be
  1870  	 * called when there has been no incomming line for 10 seconds.
  1871  	 *
  1872  	 * @param timer The timer that called this.
  1873  	 */
  1874  	protected void pingTimerTask(final Timer timer) {
        		 /* 
    P/P 		  *  Method: void pingTimerTask(Timer)
        		  * 
        		  *  Preconditions:
        		  *    this.pingNeeded != null
        		  *    (soft) this.cMyself != null
        		  *    (soft) init'ed(this.currentSocketState)
        		  *    (soft) this.pingCountDown >= -231+1
        		  *    (soft) init'ed(this.pingTimer)
        		  *    (soft) init'ed(this.stringConverter)
        		  *    (soft) this.hChanModesOther != null
        		  *    (soft) this.hChannelList != null
        		  *    (soft) this.myCallbackManager != null
        		  *    (soft) this.myCallbackManager.callbackHash != null
        		  *    ...
        		  * 
        		  *  Postconditions:
        		  *    init'ed(java.lang.String:substring(...)._tainted)
        		  *    java.lang.String:valueOf(...)._tainted == 0
        		  *    this.cMyself == old this.cMyself
        		  *    this.cMyself != null
        		  *    possibly_updated(this.cMyself.myAwayReason)
        		  *    this.currentSocketState == old this.currentSocketState
        		  *    init'ed(this.currentSocketState)
        		  *    this.got001 == old this.got001
        		  *    this.lastLine == old this.lastLine
        		  *    this.lastPingValue == One-of{old this.lastPingValue, &amp;java.lang.String:valueOf(...)}
        		  *    ...
        		  * 
        		  *  Test Vectors:
        		  *    this.pingCountDown: {2..232-1}, {-231+1..1}
        		  *    this.pingTimer: Addr_Set{null}, Inverse{null}
        		  *    call(...)@519: {1}, {0}
        		  *    java.lang.Object:equals(...)@1879: {0}, {1}
        		  *    java.util.concurrent.atomic.AtomicBoolean:get(...)@1935: {0}, {1}
        		  */
  1875  		if (getPingNeeded()) {
  1876  			if (!callPingFailed()) {
  1877  				pingTimerSem.acquireUninterruptibly();
  1878  				
  1879  				if (pingTimer != null && pingTimer.equals(timer)) {
  1880  					pingTimer.cancel();
  1881  				}
  1882  				pingTimerSem.release();
  1883  				
  1884  				disconnect("Server not responding.");
  1885  			}
  1886  		} else {
  1887  			--pingCountDown;
  1888  			if (pingCountDown < 1) {
  1889  				pingTime = System.currentTimeMillis();
  1890  				setPingNeeded(true);
  1891  				pingCountDown = pingCountDownLength;
  1892  				callPingSent();
  1893  				lastPingValue = String.valueOf(System.currentTimeMillis());
  1894  				sendLine("PING " + lastPingValue);
  1895  			}
  1896  		}
  1897  	}
  1898  
  1899  	/**
  1900  	 * Get the current server lag.
  1901  	 *
  1902  	 * @return Last time between sending a PING and recieving a PONG
  1903  	 */
  1904  	public long getServerLag() {
        		 /* 
    P/P 		  *  Method: long getServerLag()
        		  * 
        		  *  Preconditions:
        		  *    init'ed(this.serverLag)
        		  * 
        		  *  Postconditions:
        		  *    return_value == this.serverLag
        		  *    init'ed(return_value)
        		  */
  1905  		return serverLag;
  1906  	}
  1907  
  1908  	/**
  1909  	 * Get the current server lag.
  1910  	 *
  1911  	 * @param actualTime if True the value returned will be the actual time the ping was sent
  1912  	 *                   else it will be the amount of time sinse the last ping was sent.
  1913  	 * @return Time last ping was sent
  1914  	 */
  1915  	public long getPingTime(final boolean actualTime) {
        		 /* 
    P/P 		  *  Method: long getPingTime(bool)
        		  * 
        		  *  Preconditions:
        		  *    init'ed(this.pingTime)
        		  * 
        		  *  Presumptions:
        		  *    java.lang.System:currentTimeMillis(...)@1917 - this.pingTime in {-263..264-1}
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  * 
        		  *  Test Vectors:
        		  *    actualTime: {0}, {1}
        		  */
  1916  		if (actualTime) { return pingTime; }
  1917  		else { return System.currentTimeMillis() - pingTime; }
  1918  	}
  1919  
  1920  	/**
  1921  	 * Set if a ping is needed or not.
  1922  	 *
  1923  	 * @param newStatus new value to set pingNeeded to.
  1924  	 */
  1925  	private void setPingNeeded(final boolean newStatus)  {
        		 /* 
    P/P 		  *  Method: void setPingNeeded(bool)
        		  * 
        		  *  Preconditions:
        		  *    this.pingNeeded != null
        		  */
  1926  		pingNeeded.set(newStatus);
  1927  	}
  1928  
  1929  	/**
  1930  	 * Get if a ping is needed or not.
  1931  	 *
  1932  	 * @return value of pingNeeded.
  1933  	 */
  1934  	private boolean getPingNeeded()  {
        		 /* 
    P/P 		  *  Method: bool getPingNeeded()
        		  * 
        		  *  Preconditions:
        		  *    this.pingNeeded != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  */
  1935  		return pingNeeded.get();
  1936  	}
  1937  
  1938  	/**
  1939  	 * Get a reference to the cMyself object.
  1940  	 *
  1941  	 * @return cMyself reference
  1942  	 */
        	 /* 
    P/P 	  *  Method: ClientInfo getMyself()
        	  * 
        	  *  Preconditions:
        	  *    init'ed(this.cMyself)
        	  * 
        	  *  Postconditions:
        	  *    return_value == this.cMyself
        	  *    init'ed(return_value)
        	  */
  1943  	public ClientInfo getMyself() { return cMyself; }
  1944  
  1945  	/**
  1946  	 * Get the current nickname.
  1947  	 * If after 001 this returns the exact same as getMyself.getNickname();
  1948  	 * Before 001 it returns the nickname that the parser Thinks it has.
  1949  	 *
  1950  	 * @return Current nickname.
  1951  	 */
  1952  	public String getMyNickname() {
        		 /* 
    P/P 		  *  Method: String getMyNickname()
        		  * 
        		  *  Preconditions:
        		  *    this.cMyself != null
        		  *    init'ed(this.cMyself.bIsFake)
        		  *    (soft) init'ed(this.cMyself.sNickname)
        		  *    (soft) init'ed(this.sThinkNickname)
        		  * 
        		  *  Postconditions:
        		  *    return_value == One-of{this.sThinkNickname, this.cMyself.sNickname}
        		  *    init'ed(return_value)
        		  * 
        		  *  Test Vectors:
        		  *    this.cMyself.bIsFake: {0}, {1}
        		  */
  1953  		if (cMyself.isFake()) {
  1954  			return sThinkNickname;
  1955  		} else {
  1956  			return cMyself.getNickname();
  1957  		}
  1958  	}
  1959  
  1960  	/**
  1961  	 * Get the current username (Specified in MyInfo on construction).
  1962  	 * Get the username given in MyInfo
  1963  	 *
  1964  	 * @return My username.
  1965  	 */
  1966  	public String getMyUsername() {
        		 /* 
    P/P 		  *  Method: String getMyUsername()
        		  * 
        		  *  Preconditions:
        		  *    this.me != null
        		  *    init'ed(this.me.username)
        		  * 
        		  *  Postconditions:
        		  *    return_value == this.me.username
        		  *    init'ed(return_value)
        		  */
  1967  		return me.getUsername();
  1968  	}
  1969  
  1970  	/**
  1971  	 * Add a client to the ClientList.
  1972  	 *
  1973  	 * @param client Client to add
  1974  	 */
  1975  	public void addClient(final ClientInfo client) {
        		 /* 
    P/P 		  *  Method: void addClient(ClientInfo)
        		  * 
        		  *  Preconditions:
        		  *    client != null
        		  *    client.sNickname != null
        		  *    init'ed(this.stringConverter)
        		  *    this.hClientList != null
        		  *    (soft) this.stringConverter.lowercase != null
        		  *    (soft) init'ed(this.stringConverter.lowercase[...])
        		  * 
        		  *  Postconditions:
        		  *    this.stringConverter == One-of{old this.stringConverter, &amp;new IRCStringConverter(getIRCStringConverter#1)}
        		  *    this.stringConverter != null
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    new char[](IRCStringConverter#1) num objects == new IRCStringConverter(getIRCStringConverter#1) num objects
        		  *    new char[](IRCStringConverter#2) num objects == new IRCStringConverter(getIRCStringConverter#1) num objects
        		  *    new IRCStringConverter(getIRCStringConverter#1).limit == 4
        		  *    new IRCStringConverter(getIRCStringConverter#1).lowercase == &amp;new char[](IRCStringConverter#1)
        		  *    new IRCStringConverter(getIRCStringConverter#1).uppercase == &amp;new char[](IRCStringConverter#2)
        		  *    new char[](IRCStringConverter#1).length == 127
        		  *    new char[](IRCStringConverter#2).length == 127
        		  *    ...
        		  */
  1976  		hClientList.put(getIRCStringConverter().toLowerCase(client.getNickname()),client);
  1977  	}
  1978  
  1979  	/**
  1980  	 * Remove a client from the ClientList.
  1981  	 * This WILL NOT allow cMyself to be removed from the list.
  1982  	 *
  1983  	 * @param client Client to remove
  1984  	 */
  1985  	public void removeClient(final ClientInfo client) {
        		 /* 
    P/P 		  *  Method: void removeClient(ClientInfo)
        		  * 
        		  *  Preconditions:
        		  *    init'ed(this.cMyself)
        		  *    (soft) client != null
        		  *    (soft) client.sNickname != null
        		  *    (soft) init'ed(this.stringConverter)
        		  *    (soft) this.hClientList != null
        		  *    (soft) this.stringConverter.lowercase != null
        		  *    (soft) init'ed(this.stringConverter.lowercase[...])
        		  * 
        		  *  Postconditions:
        		  *    this.stringConverter == One-of{old this.stringConverter, &amp;new IRCStringConverter(getIRCStringConverter#1)}
        		  *    init'ed(this.stringConverter)
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    new IRCStringConverter(getIRCStringConverter#1).limit == 4
        		  *    new IRCStringConverter(getIRCStringConverter#1).lowercase == &amp;new char[](IRCStringConverter#1)
        		  *    new IRCStringConverter(getIRCStringConverter#1).uppercase == &amp;new char[](IRCStringConverter#2)
        		  *    new char[](IRCStringConverter#1) num objects <= 1
        		  *    new char[](IRCStringConverter#1).length == 127
        		  *    possibly_updated(new char[](IRCStringConverter#1)[...])
        		  *    new char[](IRCStringConverter#2) num objects <= 1
        		  *    ...
        		  * 
        		  *  Test Vectors:
        		  *    client == this.cMyself: {1}, {0}
        		  */
  1986  		if (client != cMyself) {
  1987  			forceRemoveClient(client);
  1988  		}
  1989  	}
  1990  
  1991  	/**
  1992  	 * Remove a client from the ClientList.
  1993  .	 * This WILL allow cMyself to be removed from the list
  1994  	 *
  1995  	 * @param client Client to remove
  1996  	 */
  1997  	protected void forceRemoveClient(final ClientInfo client) {
        		 /* 
    P/P 		  *  Method: void forceRemoveClient(ClientInfo)
        		  * 
        		  *  Preconditions:
        		  *    client != null
        		  *    client.sNickname != null
        		  *    init'ed(this.stringConverter)
        		  *    this.hClientList != null
        		  *    (soft) this.stringConverter.lowercase != null
        		  *    (soft) init'ed(this.stringConverter.lowercase[...])
        		  * 
        		  *  Postconditions:
        		  *    this.stringConverter == One-of{old this.stringConverter, &amp;new IRCStringConverter(getIRCStringConverter#1)}
        		  *    this.stringConverter != null
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    new char[](IRCStringConverter#1) num objects == new IRCStringConverter(getIRCStringConverter#1) num objects
        		  *    new char[](IRCStringConverter#2) num objects == new IRCStringConverter(getIRCStringConverter#1) num objects
        		  *    new IRCStringConverter(getIRCStringConverter#1).limit == 4
        		  *    new IRCStringConverter(getIRCStringConverter#1).lowercase == &amp;new char[](IRCStringConverter#1)
        		  *    new IRCStringConverter(getIRCStringConverter#1).uppercase == &amp;new char[](IRCStringConverter#2)
        		  *    new char[](IRCStringConverter#1).length == 127
        		  *    new char[](IRCStringConverter#2).length == 127
        		  *    ...
        		  */
  1998  		hClientList.remove(getIRCStringConverter().toLowerCase(client.getNickname()));
  1999  	}
  2000  
  2001  	/**
  2002  	 * Get the number of known clients.
  2003  	 *
  2004  	 * @return Count of known clients
  2005  	 */
  2006  	public int knownClients() {
        		 /* 
    P/P 		  *  Method: int knownClients()
        		  * 
        		  *  Preconditions:
        		  *    this.hClientList != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  */
  2007  		return hClientList.size();
  2008  	}
  2009  
  2010  	/**
  2011  	 * Get the known clients as a collection.
  2012  	 *
  2013  	 * @return Known clients as a collection
  2014  	 */
  2015  	public Collection<ClientInfo> getClients() {
        		 /* 
    P/P 		  *  Method: Collection getClients()
        		  * 
        		  *  Preconditions:
        		  *    this.hClientList != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  */
  2016  		return hClientList.values();
  2017  	}
  2018  
  2019  	/**
  2020  	 * Clear the client list.
  2021  	 */
  2022  	public void clearClients() {
        		 /* 
    P/P 		  *  Method: void clearClients()
        		  * 
        		  *  Preconditions:
        		  *    init'ed(this.stringConverter)
        		  *    this.cMyself != null
        		  *    this.cMyself.sNickname != null
        		  *    this.hClientList != null
        		  *    (soft) this.stringConverter.lowercase != null
        		  *    (soft) init'ed(this.stringConverter.lowercase[...])
        		  * 
        		  *  Postconditions:
        		  *    this.stringConverter == One-of{old this.stringConverter, &amp;new IRCStringConverter(getIRCStringConverter#1)}
        		  *    this.stringConverter != null
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    new char[](IRCStringConverter#1) num objects == new IRCStringConverter(getIRCStringConverter#1) num objects
        		  *    new char[](IRCStringConverter#2) num objects == new IRCStringConverter(getIRCStringConverter#1) num objects
        		  *    new IRCStringConverter(getIRCStringConverter#1).limit == 4
        		  *    new IRCStringConverter(getIRCStringConverter#1).lowercase == &amp;new char[](IRCStringConverter#1)
        		  *    new IRCStringConverter(getIRCStringConverter#1).uppercase == &amp;new char[](IRCStringConverter#2)
        		  *    new char[](IRCStringConverter#1).length == 127
        		  *    new char[](IRCStringConverter#2).length == 127
        		  *    ...
        		  */
  2023  		hClientList.clear();
  2024  		addClient(getMyself());
  2025  	}
  2026  
  2027  	/**
  2028  	 * Add a channel to the ChannelList.
  2029  	 *
  2030  	 * @param channel Channel to add
  2031  	 */
  2032  	public void addChannel(final ChannelInfo channel) {
        		 /* 
    P/P 		  *  Method: void addChannel(ChannelInfo)
        		  * 
        		  *  Preconditions:
        		  *    channel != null
        		  *    channel.sName != null
        		  *    init'ed(this.stringConverter)
        		  *    this.hChannelList != null
        		  *    (soft) this.stringConverter.lowercase != null
        		  *    (soft) init'ed(this.stringConverter.lowercase[...])
        		  * 
        		  *  Postconditions:
        		  *    this.stringConverter == One-of{old this.stringConverter, &amp;new IRCStringConverter(getIRCStringConverter#1)}
        		  *    this.stringConverter != null
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    new IRCStringConverter(getIRCStringConverter#1).limit == 4
        		  *    new IRCStringConverter(getIRCStringConverter#1).lowercase == &amp;new char[](IRCStringConverter#1)
        		  *    new IRCStringConverter(getIRCStringConverter#1).uppercase == &amp;new char[](IRCStringConverter#2)
        		  *    new char[](IRCStringConverter#1) num objects <= 1
        		  *    new char[](IRCStringConverter#1).length == 127
        		  *    possibly_updated(new char[](IRCStringConverter#1)[...])
        		  *    new char[](IRCStringConverter#2) num objects <= 1
        		  *    ...
        		  */
  2033  		synchronized (hChannelList) {
  2034  			hChannelList.put(getIRCStringConverter().toLowerCase(channel.getName()), channel);
  2035  		}
  2036  	}
  2037  
  2038  	/**
  2039  	 * Remove a channel from the ChannelList.
  2040  	 *
  2041  	 * @param channel Channel to remove
  2042  	 */
  2043  	public void removeChannel(final ChannelInfo channel) {
        		 /* 
    P/P 		  *  Method: void removeChannel(ChannelInfo)
        		  * 
        		  *  Preconditions:
        		  *    channel != null
        		  *    channel.sName != null
        		  *    init'ed(this.stringConverter)
        		  *    this.hChannelList != null
        		  *    (soft) this.stringConverter.lowercase != null
        		  *    (soft) init'ed(this.stringConverter.lowercase[...])
        		  * 
        		  *  Postconditions:
        		  *    this.stringConverter == One-of{old this.stringConverter, &amp;new IRCStringConverter(getIRCStringConverter#1)}
        		  *    this.stringConverter != null
        		  *    new IRCStringConverter(getIRCStringConverter#1) num objects <= 1
        		  *    new IRCStringConverter(getIRCStringConverter#1).limit == 4
        		  *    new IRCStringConverter(getIRCStringConverter#1).lowercase == &amp;new char[](IRCStringConverter#1)
        		  *    new IRCStringConverter(getIRCStringConverter#1).uppercase == &amp;new char[](IRCStringConverter#2)
        		  *    new char[](IRCStringConverter#1) num objects <= 1
        		  *    new char[](IRCStringConverter#1).length == 127
        		  *    possibly_updated(new char[](IRCStringConverter#1)[...])
        		  *    new char[](IRCStringConverter#2) num objects <= 1
        		  *    ...
        		  */
  2044  		synchronized (hChannelList) {
  2045  			hChannelList.remove(getIRCStringConverter().toLowerCase(channel.getName()));
  2046  		}
  2047  	}
  2048  
  2049  	/**
  2050  	 * Get the number of known channel.
  2051  	 *
  2052  	 * @return Count of known channel
  2053  	 */
  2054  	public int knownChannels() {
        		 /* 
    P/P 		  *  Method: int knownChannels()
        		  * 
        		  *  Preconditions:
        		  *    this.hChannelList != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  */
  2055  		synchronized (hChannelList) {
  2056  			return hChannelList.size();
  2057  		}
  2058  	}
  2059  
  2060  	/**
  2061  	 * Get the known channels as a collection.
  2062  	 *
  2063  	 * @return Known channels as a collection
  2064  	 */
  2065  	public Collection<ChannelInfo> getChannels() {
        		 /* 
    P/P 		  *  Method: Collection getChannels()
        		  * 
        		  *  Preconditions:
        		  *    this.hChannelList != null
        		  * 
        		  *  Postconditions:
        		  *    init'ed(return_value)
        		  */
  2066  		synchronized (hChannelList) {
  2067  			return hChannelList.values();
  2068  		}
  2069  	}
  2070  
  2071  	/**
  2072  	 * Clear the channel list.
  2073  	 */
  2074  	public void clearChannels() {
        		 /* 
    P/P 		  *  Method: void clearChannels()
        		  * 
        		  *  Preconditions:
        		  *    this.hChannelList != null
        		  */
  2075  		synchronized (hChannelList) {
  2076  			hChannelList.clear();
  2077  		}
  2078  	}
  2079  
  2080  }








SofCheck Inspector Build Version : 2.17854
IRCParser.java 2009-Jun-25 01:54:24
IRCParser.class 2009-Sep-02 17:04:12
IRCParser$1.class 2009-Sep-02 17:04:12