    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.net;
    019
    020import java.io.Closeable;
    021import java.io.IOException;
    022import java.io.InputStream;
    023import java.io.OutputStream;
    024import java.net.InetAddress;
    025import java.net.InetSocketAddress;
    026import java.net.Proxy;
    027import java.net.Socket;
    028import java.net.SocketException;
    029import java.nio.charset.Charset;
    030
    031import javax.net.ServerSocketFactory;
    032import javax.net.SocketFactory;
    033
    034
    035/**
    036 * The SocketClient provides the basic operations that are required of
    037 * client objects accessing sockets.  It is meant to be
    038 * subclassed to avoid having to rewrite the same code over and over again
    039 * to open a socket, close a socket, set timeouts, etc.  Of special note
    040 * is the {@link #setSocketFactory  setSocketFactory }
    041 * method, which allows you to control the type of Socket the SocketClient
    042 * creates for initiating network connections.  This is especially useful
    043 * for adding SSL or proxy support as well as better support for applets.  For
    044 * example, you could create a
    045 * {@link javax.net.SocketFactory} that
    046 * requests browser security capabilities before creating a socket.
    047 * All classes derived from SocketClient should use the
    048 * {@link #_socketFactory_  _socketFactory_ } member variable to
    049 * create Socket and ServerSocket instances rather than instantiating
    050 * them by directly invoking a constructor.  By honoring this contract
    051 * you guarantee that a user will always be able to provide his own
    052 * Socket implementations by substituting his own SocketFactory.
    053 * @see SocketFactory
    054 */
    055public abstract class SocketClient
    056{
    057    /**
    058     * The end of line character sequence used by most IETF protocols.  That
    059     * is a carriage return followed by a newline: "\r\n"
    060     */
    061    public static final String NETASCII_EOL = "\r\n";
    062
    063    /** The default SocketFactory shared by all SocketClient instances. */
    064    private static final SocketFactory __DEFAULT_SOCKET_FACTORY =
    065            SocketFactory.getDefault();
    066
    067    /** The default {@link ServerSocketFactory} */
    068    private static final ServerSocketFactory __DEFAULT_SERVER_SOCKET_FACTORY =
    069            ServerSocketFactory.getDefault();
    070
    071    /**
    072     * A ProtocolCommandSupport object used to manage the registering of
    073     * ProtocolCommandListeners and the firing of ProtocolCommandEvents.
    074     */
    075    private ProtocolCommandSupport __commandSupport;
    076
    077    /** The timeout to use after opening a socket. */
    078    protected int _timeout_;
    079
    080    /** The socket used for the connection. */
    081    protected Socket _socket_;
    082
    083    /** The default port the client should connect to. */
    084    protected int _defaultPort_;
    085
    086    /** The socket's InputStream. */
    087    protected InputStream _input_;
    088
    089    /** The socket's OutputStream. */
    090    protected OutputStream _output_;
    091
    092    /** The socket's SocketFactory. */
    093    protected SocketFactory _socketFactory_;
    094
    095    /** The socket's ServerSocket Factory. */
    096    protected ServerSocketFactory _serverSocketFactory_;
    097
    098    /** The socket's connect timeout (0 = infinite timeout) */
    099    private static final int DEFAULT_CONNECT_TIMEOUT = 0;
    100    protected int connectTimeout = DEFAULT_CONNECT_TIMEOUT;
    101
    102    /** Hint for SO_RCVBUF size */
    103    private int receiveBufferSize = -1;
    104
    105    /** Hint for SO_SNDBUF size */
    106    private int sendBufferSize = -1;
    107
    108    /** The proxy to use when connecting. */
    109    private Proxy connProxy;
    110
    111    /**
    112     * Charset to use for byte IO.
    113     */
    114    private Charset charset = Charset.defaultCharset();
    115
    116    /**
    117     * Default constructor for SocketClient.  Initializes
    118     * _socket_ to null, _timeout_ to 0, _defaultPort to 0,
    119     * _isConnected_ to false, charset to {@code Charset.defaultCharset()}
    120     * and _socketFactory_ to a shared instance of
    121     * {@link org.apache.commons.net.DefaultSocketFactory}.
    122     */
    123    public SocketClient()
    124    {
    125        _socket_ = null;
    126        _input_ = null;
    127        _output_ = null;
    128        _timeout_ = 0;
    129        _defaultPort_ = 0;
    130        _socketFactory_ = __DEFAULT_SOCKET_FACTORY;
    131        _serverSocketFactory_ = __DEFAULT_SERVER_SOCKET_FACTORY;
    132    }
    133
    134
    135    /**
    136     * Because there are so many connect() methods, the _connectAction_()
    137     * method is provided as a means of performing some action immediately
    138     * after establishing a connection, rather than reimplementing all
    139     * of the connect() methods.  The last action performed by every
    140     * connect() method after opening a socket is to call this method.
    141     * <p>
    142     * This method sets the timeout on the just opened socket to the default
    143     * timeout set by {@link #setDefaultTimeout  setDefaultTimeout() },
    144     * sets _input_ and _output_ to the socket's InputStream and OutputStream
    145     * respectively, and sets _isConnected_ to true.
    146     * <p>
    147     * Subclasses overriding this method should start by calling
    148     * <code> super._connectAction_() </code> first to ensure the
    149     * initialization of the aforementioned protected variables.
    150     */
    151    protected void _connectAction_() throws IOException
    152    {
    153        _socket_.setSoTimeout(_timeout_);
    154        _input_ = _socket_.getInputStream();
    155        _output_ = _socket_.getOutputStream();
    156    }
    157
    158
    159    /**
    160     * Opens a Socket connected to a remote host at the specified port and
    161     * originating from the current host at a system assigned port.
    162     * Before returning, {@link #_connectAction_  _connectAction_() }
    163     * is called to perform connection initialization actions.
    164     * <p>
    165     * @param host  The remote host.
    166     * @param port  The port to connect to on the remote host.
    167     * @exception SocketException If the socket timeout could not be set.
    168     * @exception IOException If the socket could not be opened.  In most
    169     *  cases you will only want to catch IOException since SocketException is
    170     *  derived from it.
    171     */
    172    public void connect(InetAddress host, int port)
    173    throws SocketException, IOException
    174    {
    175        _socket_ = _socketFactory_.createSocket();
    176        if (receiveBufferSize != -1) {
    177            _socket_.setReceiveBufferSize(receiveBufferSize);
    178        }
    179        if (sendBufferSize != -1) {
    180            _socket_.setSendBufferSize(sendBufferSize);
    181        }
    182        _socket_.connect(new InetSocketAddress(host, port), connectTimeout);
    183        _connectAction_();
    184    }
    185
    186    /**
    187     * Opens a Socket connected to a remote host at the specified port and
    188     * originating from the current host at a system assigned port.
    189     * Before returning, {@link #_connectAction_  _connectAction_() }
    190     * is called to perform connection initialization actions.
    191     * <p>
    192     * @param hostname  The name of the remote host.
    193     * @param port  The port to connect to on the remote host.
    194     * @exception SocketException If the socket timeout could not be set.
    195     * @exception IOException If the socket could not be opened.  In most
    196     *  cases you will only want to catch IOException since SocketException is
    197     *  derived from it.
    198     * @exception java.net.UnknownHostException If the hostname cannot be resolved.
    199     */
    200    public void connect(String hostname, int port)
    201    throws SocketException, IOException
    202    {
    203        connect(InetAddress.getByName(hostname), port);
    204    }
    205
    206
    207    /**
    208     * Opens a Socket connected to a remote host at the specified port and
    209     * originating from the specified local address and port.
    210     * Before returning, {@link #_connectAction_  _connectAction_() }
    211     * is called to perform connection initialization actions.
    212     * <p>
    213     * @param host  The remote host.
    214     * @param port  The port to connect to on the remote host.
    215     * @param localAddr  The local address to use.
    216     * @param localPort  The local port to use.
    217     * @exception SocketException If the socket timeout could not be set.
    218     * @exception IOException If the socket could not be opened.  In most
    219     *  cases you will only want to catch IOException since SocketException is
    220     *  derived from it.
    221     */
    222    public void connect(InetAddress host, int port,
    223                        InetAddress localAddr, int localPort)
    224    throws SocketException, IOException
    225    {
    226        _socket_ = _socketFactory_.createSocket();
    227        if (receiveBufferSize != -1) {
    228            _socket_.setReceiveBufferSize(receiveBufferSize);
    229        }
    230        if (sendBufferSize != -1) {
    231            _socket_.setSendBufferSize(sendBufferSize);
    232        }
    233        _socket_.bind(new InetSocketAddress(localAddr, localPort));
    234        _socket_.connect(new InetSocketAddress(host, port), connectTimeout);
    235        _connectAction_();
    236    }
    237
    238
    239    /**
    240     * Opens a Socket connected to a remote host at the specified port and
    241     * originating from the specified local address and port.
    242     * Before returning, {@link #_connectAction_  _connectAction_() }
    243     * is called to perform connection initialization actions.
    244     * <p>
    245     * @param hostname  The name of the remote host.
    246     * @param port  The port to connect to on the remote host.
    247     * @param localAddr  The local address to use.
    248     * @param localPort  The local port to use.
    249     * @exception SocketException If the socket timeout could not be set.
    250     * @exception IOException If the socket could not be opened.  In most
    251     *  cases you will only want to catch IOException since SocketException is
    252     *  derived from it.
    253     * @exception java.net.UnknownHostException If the hostname cannot be resolved.
    254     */
    255    public void connect(String hostname, int port,
    256                        InetAddress localAddr, int localPort)
    257    throws SocketException, IOException
    258    {
    259       connect(InetAddress.getByName(hostname), port, localAddr, localPort);
    260    }
    261
    262
    263    /**
    264     * Opens a Socket connected to a remote host at the current default port
    265     * and originating from the current host at a system assigned port.
    266     * Before returning, {@link #_connectAction_  _connectAction_() }
    267     * is called to perform connection initialization actions.
    268     * <p>
    269     * @param host  The remote host.
    270     * @exception SocketException If the socket timeout could not be set.
    271     * @exception IOException If the socket could not be opened.  In most
    272     *  cases you will only want to catch IOException since SocketException is
    273     *  derived from it.
    274     */
    275    public void connect(InetAddress host) throws SocketException, IOException
    276    {
    277        connect(host, _defaultPort_);
    278    }
    279
    280
    281    /**
    282     * Opens a Socket connected to a remote host at the current default
    283     * port and originating from the current host at a system assigned port.
    284     * Before returning, {@link #_connectAction_  _connectAction_() }
    285     * is called to perform connection initialization actions.
    286     * <p>
    287     * @param hostname  The name of the remote host.
    288     * @exception SocketException If the socket timeout could not be set.
    289     * @exception IOException If the socket could not be opened.  In most
    290     *  cases you will only want to catch IOException since SocketException is
    291     *  derived from it.
    292     * @exception java.net.UnknownHostException If the hostname cannot be resolved.
    293     */
    294    public void connect(String hostname) throws SocketException, IOException
    295    {
    296        connect(hostname, _defaultPort_);
    297    }
    298
    299
    300    /**
    301     * Disconnects the socket connection.
    302     * You should call this method after you've finished using the class
    303     * instance and also before you call
    304     * {@link #connect connect() }
    305     * again.  _isConnected_ is set to false, _socket_ is set to null,
    306     * _input_ is set to null, and _output_ is set to null.
    307     * <p>
    308     * @exception IOException  If there is an error closing the socket.
    309     */
    310    public void disconnect() throws IOException
    311    {
    312        closeQuietly(_socket_);
    313        closeQuietly(_input_);
    314        closeQuietly(_output_);
    315        _socket_ = null;
    316        _input_ = null;
    317        _output_ = null;
    318    }
    319
    320    private void closeQuietly(Socket socket) {
    321        if (socket != null){
    322            try {
    323                socket.close();
    324            } catch (IOException e) {
    325            }
    326        }
    327    }
    328
    329    private void closeQuietly(Closeable close){
    330        if (close != null){
    331            try {
    332                close.close();
    333            } catch (IOException e) {
    334            }
    335        }
    336    }
    337    /**
    338     * Returns true if the client is currently connected to a server.
    339     * <p>
    340     * Delegates to {@link Socket#isConnected()}
    341     * @return True if the client is currently connected to a server,
    342     *         false otherwise.
    343     */
    344    public boolean isConnected()
    345    {
    346        if (_socket_ == null) {
    347            return false;
    348        }
    349
    350        return _socket_.isConnected();
    351    }
    352
    353    /**
    354     * Make various checks on the socket to test if it is available for use.
    355     * Note that the only sure test is to use it, but these checks may help
    356     * in some cases.
    357     * @see <a href="https://issues.apache.org/jira/browse/NET-350">NET-350</a>
    358     * @return {@code true} if the socket appears to be available for use
    359     * @since 3.0
    360     */
    361    public boolean isAvailable(){
    362        if (isConnected()) {
    363            try
    364            {
    365                if (_socket_.getInetAddress() == null) {
    366                    return false;
    367                }
    368                if (_socket_.getPort() == 0) {
    369                    return false;
    370                }
    371                if (_socket_.getRemoteSocketAddress() == null) {
    372                    return false;
    373                }
    374                if (_socket_.isClosed()) {
    375                    return false;
    376                }
    377                /* these aren't exact checks (a Socket can be half-open),
    378                   but since we usually require two-way data transfer,
    379                   we check these here too: */
    380                if (_socket_.isInputShutdown()) {
    381                    return false;
    382                }
    383                if (_socket_.isOutputShutdown()) {
    384                    return false;
    385                }
    386                /* ignore the result, catch exceptions: */
    387                _socket_.getInputStream();
    388                _socket_.getOutputStream();
    389            }
    390            catch (IOException ioex)
    391            {
    392                return false;
    393            }
    394            return true;
    395        } else {
    396            return false;
    397        }
    398    }
    399
    400    /**
    401     * Sets the default port the SocketClient should connect to when a port
    402     * is not specified.  The {@link #_defaultPort_  _defaultPort_ }
    403     * variable stores this value.  If never set, the default port is equal
    404     * to zero.
    405     * <p>
    406     * @param port  The default port to set.
    407     */
    408    public void setDefaultPort(int port)
    409    {
    410        _defaultPort_ = port;
    411    }
    412
    413    /**
    414     * Returns the current value of the default port (stored in
    415     * {@link #_defaultPort_  _defaultPort_ }).
    416     * <p>
    417     * @return The current value of the default port.
    418     */
    419    public int getDefaultPort()
    420    {
    421        return _defaultPort_;
    422    }
    423
    424
    425    /**
    426     * Set the default timeout in milliseconds to use when opening a socket.
    427     * This value is only used previous to a call to
    428     * {@link #connect connect()}
    429     * and should not be confused with {@link #setSoTimeout setSoTimeout()}
    430     * which operates on an the currently opened socket.  _timeout_ contains
    431     * the new timeout value.
    432     * <p>
    433     * @param timeout  The timeout in milliseconds to use for the socket
    434     *                 connection.
    435     */
    436    public void setDefaultTimeout(int timeout)
    437    {
    438        _timeout_ = timeout;
    439    }
    440
    441
    442    /**
    443     * Returns the default timeout in milliseconds that is used when
    444     * opening a socket.
    445     * <p>
    446     * @return The default timeout in milliseconds that is used when
    447     *         opening a socket.
    448     */
    449    public int getDefaultTimeout()
    450    {
    451        return _timeout_;
    452    }
    453
    454
    455    /**
    456     * Set the timeout in milliseconds of a currently open connection.
    457     * Only call this method after a connection has been opened
    458     * by {@link #connect connect()}.
    459     * <p>
    460     * To set the initial timeout, use {@link #setDefaultTimeout(int)} instead.
    461     *
    462     * @param timeout  The timeout in milliseconds to use for the currently
    463     *                 open socket connection.
    464     * @exception SocketException If the operation fails.
    465     * @throws NullPointerException if the socket is not currently open
    466     */
    467    public void setSoTimeout(int timeout) throws SocketException
    468    {
    469        _socket_.setSoTimeout(timeout);
    470    }
    471
    472
    473    /**
    474     * Set the underlying socket send buffer size.
    475     * <p>
    476     * @param size The size of the buffer in bytes.
    477     * @throws SocketException
    478     * @since 2.0
    479     */
    480    public void setSendBufferSize(int size) throws SocketException {
    481        sendBufferSize = size;
    482    }
    483
    484    /**
    485     * Get the current sendBuffer size
    486     * @return the size, or -1 if not initialised
    487     * @since 3.0
    488     */
    489    protected int getSendBufferSize(){
    490        return sendBufferSize;
    491    }
    492
    493    /**
    494     * Sets the underlying socket receive buffer size.
    495     * <p>
    496     * @param size The size of the buffer in bytes.
    497     * @throws SocketException
    498     * @since 2.0
    499     */
    500    public void setReceiveBufferSize(int size) throws SocketException  {
    501        receiveBufferSize = size;
    502    }
    503
    504    /**
    505     * Get the current receivedBuffer size
    506     * @return the size, or -1 if not initialised
    507     * @since 3.0
    508     */
    509    protected int getReceiveBufferSize(){
    510        return receiveBufferSize;
    511    }
    512
    513    /**
    514     * Returns the timeout in milliseconds of the currently opened socket.
    515     * <p>
    516     * @return The timeout in milliseconds of the currently opened socket.
    517     * @exception SocketException If the operation fails.
    518     * @throws NullPointerException if the socket is not currently open
    519     */
    520    public int getSoTimeout() throws SocketException
    521    {
    522        return _socket_.getSoTimeout();
    523    }
    524
    525    /**
    526     * Enables or disables the Nagle's algorithm (TCP_NODELAY) on the
    527     * currently opened socket.
    528     * <p>
    529     * @param on  True if Nagle's algorithm is to be enabled, false if not.
    530     * @exception SocketException If the operation fails.
    531     * @throws NullPointerException if the socket is not currently open
    532     */
    533    public void setTcpNoDelay(boolean on) throws SocketException
    534    {
    535        _socket_.setTcpNoDelay(on);
    536    }
    537
    538
    539    /**
    540     * Returns true if Nagle's algorithm is enabled on the currently opened
    541     * socket.
    542     * <p>
    543     * @return True if Nagle's algorithm is enabled on the currently opened
    544     *        socket, false otherwise.
    545     * @exception SocketException If the operation fails.
    546     * @throws NullPointerException if the socket is not currently open
    547     */
    548    public boolean getTcpNoDelay() throws SocketException
    549    {
    550        return _socket_.getTcpNoDelay();
    551    }
    552
    553    /**
    554     * Sets the SO_KEEPALIVE flag on the currently opened socket.
    555     *
    556     * From the Javadocs, the default keepalive time is 2 hours (although this is
    557     * implementation  dependent). It looks as though the Windows WSA sockets implementation
    558     * allows a specific keepalive value to be set, although this seems not to be the case on
    559     * other systems.
    560     * @param  keepAlive If true, keepAlive is turned on
    561     * @throws SocketException
    562     * @throws NullPointerException if the socket is not currently open
    563     * @since 2.2
    564     */
    565    public void setKeepAlive(boolean keepAlive) throws SocketException {
    566        _socket_.setKeepAlive(keepAlive);
    567    }
    568
    569    /**
    570     * Returns the current value of the SO_KEEPALIVE flag on the currently opened socket.
    571     * Delegates to {@link Socket#getKeepAlive()}
    572     * @return True if SO_KEEPALIVE is enabled.
    573     * @throws SocketException
    574     * @throws NullPointerException if the socket is not currently open
    575     * @since 2.2
    576     */
    577    public boolean getKeepAlive() throws SocketException {
    578        return _socket_.getKeepAlive();
    579    }
    580
    581    /**
    582     * Sets the SO_LINGER timeout on the currently opened socket.
    583     * <p>
    584     * @param on  True if linger is to be enabled, false if not.
    585     * @param val The linger timeout (in hundredths of a second?)
    586     * @exception SocketException If the operation fails.
    587     * @throws NullPointerException if the socket is not currently open
    588     */
    589    public void setSoLinger(boolean on, int val) throws SocketException
    590    {
    591        _socket_.setSoLinger(on, val);
    592    }
    593
    594
    595    /**
    596     * Returns the current SO_LINGER timeout of the currently opened socket.
    597     * <p>
    598     * @return The current SO_LINGER timeout.  If SO_LINGER is disabled returns
    599     *         -1.
    600     * @exception SocketException If the operation fails.
    601     * @throws NullPointerException if the socket is not currently open
    602     */
    603    public int getSoLinger() throws SocketException
    604    {
    605        return _socket_.getSoLinger();
    606    }
    607
    608
    609    /**
    610     * Returns the port number of the open socket on the local host used
    611     * for the connection.
    612     * Delegates to {@link Socket#getLocalPort()}
    613     * <p>
    614     * @return The port number of the open socket on the local host used
    615     *         for the connection.
    616     * @throws NullPointerException if the socket is not currently open
    617     */
    618    public int getLocalPort()
    619    {
    620        return _socket_.getLocalPort();
    621    }
    622
    623
    624    /**
    625     * Returns the local address  to which the client's socket is bound.
    626     * Delegates to {@link Socket#getLocalAddress()}
    627     * <p>
    628     * @return The local address to which the client's socket is bound.
    629     * @throws NullPointerException if the socket is not currently open
    630     */
    631    public InetAddress getLocalAddress()
    632    {
    633        return _socket_.getLocalAddress();
    634    }
    635
    636    /**
    637     * Returns the port number of the remote host to which the client is
    638     * connected.
    639     * Delegates to {@link Socket#getPort()}
    640     * <p>
    641     * @return The port number of the remote host to which the client is
    642     *         connected.
    643     * @throws NullPointerException if the socket is not currently open
    644     */
    645    public int getRemotePort()
    646    {
    647        return _socket_.getPort();
    648    }
    649
    650
    651    /**
    652     * @return The remote address to which the client is connected.
    653     * Delegates to {@link Socket#getInetAddress()}
    654     * @throws NullPointerException if the socket is not currently open
    655     */
    656    public InetAddress getRemoteAddress()
    657    {
    658        return _socket_.getInetAddress();
    659    }
    660
    661
    662    /**
    663     * Verifies that the remote end of the given socket is connected to the
    664     * the same host that the SocketClient is currently connected to.  This
    665     * is useful for doing a quick security check when a client needs to
    666     * accept a connection from a server, such as an FTP data connection or
    667     * a BSD R command standard error stream.
    668     * <p>
    669     * @return True if the remote hosts are the same, false if not.
    670     */
    671    public boolean verifyRemote(Socket socket)
    672    {
    673        InetAddress host1, host2;
    674
    675        host1 = socket.getInetAddress();
    676        host2 = getRemoteAddress();
    677
    678        return host1.equals(host2);
    679    }
    680
    681
    682    /**
    683     * Sets the SocketFactory used by the SocketClient to open socket
    684     * connections.  If the factory value is null, then a default
    685     * factory is used (only do this to reset the factory after having
    686     * previously altered it).
    687     * Any proxy setting is discarded.
    688     * <p>
    689     * @param factory  The new SocketFactory the SocketClient should use.
    690     */
    691    public void setSocketFactory(SocketFactory factory)
    692    {
    693        if (factory == null) {
    694            _socketFactory_ = __DEFAULT_SOCKET_FACTORY;
    695        } else {
    696            _socketFactory_ = factory;
    697        }
    698        // re-setting the socket factory makes the proxy setting useless,
    699        // so set the field to null so that getProxy() doesn't return a
    700        // Proxy that we're actually not using.
    701        connProxy = null;
    702    }
    703
    704    /**
    705     * Sets the ServerSocketFactory used by the SocketClient to open ServerSocket
    706     * connections.  If the factory value is null, then a default
    707     * factory is used (only do this to reset the factory after having
    708     * previously altered it).
    709     * <p>
    710     * @param factory  The new ServerSocketFactory the SocketClient should use.
    711     * @since 2.0
    712     */
    713    public void setServerSocketFactory(ServerSocketFactory factory) {
    714        if (factory == null) {
    715            _serverSocketFactory_ = __DEFAULT_SERVER_SOCKET_FACTORY;
    716        } else {
    717            _serverSocketFactory_ = factory;
    718        }
    719    }
    720
    721    /**
    722     * Sets the connection timeout in milliseconds, which will be passed to the {@link Socket} object's
    723     * connect() method.
    724     * @param connectTimeout The connection timeout to use (in ms)
    725     * @since 2.0
    726     */
    727    public void setConnectTimeout(int connectTimeout) {
    728        this.connectTimeout = connectTimeout;
    729    }
    730
    731    /**
    732     * Get the underlying socket connection timeout.
    733     * @return timeout (in ms)
    734     * @since 2.0
    735     */
    736    public int getConnectTimeout() {
    737        return connectTimeout;
    738    }
    739
    740    /**
    741     * Get the underlying {@link ServerSocketFactory}
    742     * @return The server socket factory
    743     * @since 2.2
    744     */
    745    public ServerSocketFactory getServerSocketFactory() {
    746        return _serverSocketFactory_;
    747    }
    748
    749
    750    /**
    751     * Adds a ProtocolCommandListener.
    752     *
    753     * @param listener  The ProtocolCommandListener to add.
    754     * @since 3.0
    755     */
    756    public void addProtocolCommandListener(ProtocolCommandListener listener) {
    757        getCommandSupport().addProtocolCommandListener(listener);
    758    }
    759
    760    /**
    761     * Removes a ProtocolCommandListener.
    762     *
    763     * @param listener  The ProtocolCommandListener to remove.
    764     * @since 3.0
    765     */
    766    public void removeProtocolCommandListener(ProtocolCommandListener listener) {
    767        getCommandSupport().removeProtocolCommandListener(listener);
    768    }
    769
    770    /**
    771     * If there are any listeners, send them the reply details.
    772     *
    773     * @param replyCode the code extracted from the reply
    774     * @param reply the full reply text
    775     * @since 3.0
    776     */
    777    protected void fireReplyReceived(int replyCode, String reply) {
    778        if (getCommandSupport().getListenerCount() > 0) {
    779            getCommandSupport().fireReplyReceived(replyCode, reply);
    780        }
    781    }
    782
    783    /**
    784     * If there are any listeners, send them the command details.
    785     *
    786     * @param command the command name
    787     * @param message the complete message, including command name
    788     * @since 3.0
    789     */
    790    protected void fireCommandSent(String command, String message) {
    791        if (getCommandSupport().getListenerCount() > 0) {
    792            getCommandSupport().fireCommandSent(command, message);
    793        }
    794    }
    795
    796    /**
    797     * Create the CommandSupport instance if required
    798     */
    799    protected void createCommandSupport(){
    800        __commandSupport = new ProtocolCommandSupport(this);
    801    }
    802
    803    /**
    804     * Subclasses can override this if they need to provide their own
    805     * instance field for backwards compatibilty.
    806     *
    807     * @return the CommandSupport instance, may be {@code null}
    808     * @since 3.0
    809     */
    810    protected ProtocolCommandSupport getCommandSupport() {
    811        return __commandSupport;
    812    }
    813
    814    /**
    815     * Sets the proxy for use with all the connections.
    816     * The proxy is used for connections established after the
    817     * call to this method.
    818     *
    819     * @param proxy the new proxy for connections.
    820     * @since 3.2
    821     */
    822    public void setProxy(Proxy proxy) {
    823        setSocketFactory(new DefaultSocketFactory(proxy));
    824        connProxy = proxy;
    825    }
    826
    827    /**
    828     * Gets the proxy for use with all the connections.
    829     * @return the current proxy for connections.
    830     */
    831    public Proxy getProxy() {
    832        return connProxy;
    833    }
    834
    835    /**
    836     * Gets the charset name.
    837     *
    838     * @return the charset.
    839     * @since 3.3
    840     * TODO Will be deprecated once the code requires Java 1.6 as a mininmum
    841     */
    842    public String getCharsetName() {
    843        return charset.name();
    844    }
    845
    846    /**
    847     * Gets the charset.
    848     *
    849     * @return the charset.
    850     * @since 3.3
    851     */
    852    public Charset getCharset() {
    853        return charset;
    854    }
    855
    856    /**
    857     * Sets the charset.
    858     *
    859     * @param charset the charset.
    860     * @since 3.3
    861     */
    862    public void setCharset(Charset charset) {
    863        this.charset = charset;
    864    }
    865
    866    /*
    867     *  N.B. Fields cannot be pulled up into a super-class without breaking binary compatibility,
    868     *  so the abstract method is needed to pass the instance to the methods which were moved here.
    869     */
    870}
    871
    872