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