    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.net.DatagramSocket;
    021import java.net.InetAddress;
    022import java.net.SocketException;
    023import java.nio.charset.Charset;
    024import java.time.Duration;
    025import java.util.Objects;
    026
    027/**
    028 * The DatagramSocketClient provides the basic operations that are required of client objects accessing datagram sockets. It is meant to be subclassed to avoid
    029 * having to rewrite the same code over and over again to open a socket, close a socket, set timeouts, etc. Of special note is the
    030 * {@link #setDatagramSocketFactory setDatagramSocketFactory } method, which allows you to control the type of DatagramSocket the DatagramSocketClient creates
    031 * for network communications. This is especially useful for adding things like proxy support as well as better support for applets. For example, you could
    032 * create a {@link org.apache.commons.net.DatagramSocketFactory} that requests browser security capabilities before creating a socket. All classes derived from
    033 * DatagramSocketClient should use the {@link #_socketFactory_ _socketFactory_ } member variable to create DatagramSocket instances rather than instantiating
    034 * them by directly invoking a constructor. By honoring this contract you guarantee that a user will always be able to provide his own Socket implementations by
    035 * substituting his own SocketFactory.
    036 *
    037 *
    038 * @see DatagramSocketFactory
    039 */
    040
    041public abstract class DatagramSocketClient implements AutoCloseable {
    042    /**
    043     * The default DatagramSocketFactory shared by all DatagramSocketClient instances.
    044     */
    045    private static final DatagramSocketFactory DEFAULT_SOCKET_FACTORY = new DefaultDatagramSocketFactory();
    046
    047    /**
    048     * Charset to use for byte IO.
    049     */
    050    private Charset charset = Charset.defaultCharset();
    051
    052    /** The timeout to use after opening a socket. */
    053    protected int _timeout_;
    054
    055    /** The datagram socket used for the connection. */
    056    protected DatagramSocket _socket_;
    057
    058    /**
    059     * A status variable indicating if the client's socket is currently open.
    060     */
    061    protected boolean _isOpen_;
    062
    063    /** The datagram socket's DatagramSocketFactory. */
    064    protected DatagramSocketFactory _socketFactory_ = DEFAULT_SOCKET_FACTORY;
    065
    066    /**
    067     * Default constructor for DatagramSocketClient. Initializes _socket_ to null, _timeout_ to 0, and _isOpen_ to false.
    068     */
    069    public DatagramSocketClient() {
    070    }
    071
    072    /**
    073     * Gets the non-null DatagramSocket or throws {@link NullPointerException}.
    074     *
    075     * <p>
    076     * This method does not allocate resources.
    077     * </p>
    078     *
    079     * @return the non-null DatagramSocket.
    080     * @since 3.10.0
    081     */
    082    protected DatagramSocket checkOpen() {
    083        return Objects.requireNonNull(_socket_, "DatagramSocket");
    084    }
    085
    086    /**
    087     * Closes the DatagramSocket used for the connection. You should call this method after you've finished using the class instance and also before you call
    088     * {@link #open open() } again. _isOpen_ is set to false and _socket_ is set to null.
    089     */
    090    @Override
    091    public void close() {
    092        if (_socket_ != null) {
    093            _socket_.close();
    094        }
    095        _socket_ = null;
    096        _isOpen_ = false;
    097    }
    098
    099    /**
    100     * Gets the charset.
    101     *
    102     * @return the charset.
    103     * @since 3.3
    104     */
    105    public Charset getCharset() {
    106        return charset;
    107    }
    108
    109    /**
    110     * Gets the charset name.
    111     *
    112     * @return the charset name.
    113     * @since 3.3
    114     * @deprecated Use {@link #getCharset()} instead
    115     */
    116    @Deprecated
    117    public String getCharsetName() {
    118        return charset.name();
    119    }
    120
    121    /**
    122     * Gets the default timeout in milliseconds that is used when opening a socket.
    123     *
    124     * @return The default timeout in milliseconds that is used when opening a socket.
    125     */
    126    public int getDefaultTimeout() {
    127        return _timeout_;
    128    }
    129
    130    /**
    131     * Gets the local address to which the client's socket is bound. If you call this method when the client socket is not open, a NullPointerException is
    132     * thrown.
    133     *
    134     * @return The local address to which the client's socket is bound.
    135     */
    136    public InetAddress getLocalAddress() {
    137        return checkOpen().getLocalAddress();
    138    }
    139
    140    /**
    141     * Gets the port number of the open socket on the local host used for the connection. If you call this method when the client socket is not open, a
    142     * NullPointerException is thrown.
    143     *
    144     * @return The port number of the open socket on the local host used for the connection.
    145     */
    146    public int getLocalPort() {
    147        return checkOpen().getLocalPort();
    148    }
    149
    150    /**
    151     * Gets the timeout in milliseconds of the currently opened socket. If you call this method when the client socket is not open, a NullPointerException is
    152     * thrown.
    153     *
    154     * @return The timeout in milliseconds of the currently opened socket.
    155     * @throws SocketException if an error getting the timeout.
    156     * @deprecated Use {@link #getSoTimeoutDuration()}.
    157     */
    158    @Deprecated
    159    public int getSoTimeout() throws SocketException {
    160        return checkOpen().getSoTimeout();
    161    }
    162
    163    /**
    164     * Gets the timeout duration of the currently opened socket. If you call this method when the client socket is not open, a NullPointerException is
    165     * thrown.
    166     *
    167     * @return The timeout in milliseconds of the currently opened socket.
    168     * @throws SocketException if an error getting the timeout.
    169     */
    170    public Duration getSoTimeoutDuration() throws SocketException {
    171        return Duration.ofMillis(checkOpen().getSoTimeout());
    172    }
    173
    174    /**
    175     * Gets true if the client has a currently open socket.
    176     *
    177     * @return True if the client has a currently open socket, false otherwise.
    178     */
    179    public boolean isOpen() {
    180        return _isOpen_;
    181    }
    182
    183    /**
    184     * Opens a DatagramSocket on the local host at the first available port. Also sets the timeout on the socket to the default timeout set by
    185     * {@link #setDefaultTimeout setDefaultTimeout() }.
    186     * <p>
    187     * _isOpen_ is set to true after calling this method and _socket_ is set to the newly opened socket.
    188     *
    189     * @throws SocketException If the socket could not be opened or the timeout could not be set.
    190     */
    191    public void open() throws SocketException {
    192        _socket_ = _socketFactory_.createDatagramSocket();
    193        _socket_.setSoTimeout(_timeout_);
    194        _isOpen_ = true;
    195    }
    196
    197    /**
    198     * Opens a DatagramSocket on the local host at a specified port. Also sets the timeout on the socket to the default timeout set by {@link #setDefaultTimeout
    199     * setDefaultTimeout() }.
    200     * <p>
    201     * _isOpen_ is set to true after calling this method and _socket_ is set to the newly opened socket.
    202     *
    203     * @param port The port to use for the socket.
    204     * @throws SocketException If the socket could not be opened or the timeout could not be set.
    205     */
    206    public void open(final int port) throws SocketException {
    207        _socket_ = _socketFactory_.createDatagramSocket(port);
    208        _socket_.setSoTimeout(_timeout_);
    209        _isOpen_ = true;
    210    }
    211
    212    /**
    213     * Opens a DatagramSocket at the specified address on the local host at a specified port. Also sets the timeout on the socket to the default timeout set by
    214     * {@link #setDefaultTimeout setDefaultTimeout() }.
    215     * <p>
    216     * _isOpen_ is set to true after calling this method and _socket_ is set to the newly opened socket.
    217     *
    218     * @param port  The port to use for the socket.
    219     * @param localAddress The local address to use.
    220     * @throws SocketException If the socket could not be opened or the timeout could not be set.
    221     */
    222    public void open(final int port, final InetAddress localAddress) throws SocketException {
    223        _socket_ = _socketFactory_.createDatagramSocket(port, localAddress);
    224        _socket_.setSoTimeout(_timeout_);
    225        _isOpen_ = true;
    226    }
    227
    228    /**
    229     * Sets the charset.
    230     *
    231     * @param charset the charset.
    232     * @since 3.3
    233     */
    234    public void setCharset(final Charset charset) {
    235        this.charset = charset;
    236    }
    237
    238    /**
    239     * Sets the DatagramSocketFactory used by the DatagramSocketClient to open DatagramSockets. If the factory value is null, then a default factory is used
    240     * (only do this to reset the factory after having previously altered it).
    241     *
    242     * @param factory The new DatagramSocketFactory the DatagramSocketClient should use.
    243     */
    244    public void setDatagramSocketFactory(final DatagramSocketFactory factory) {
    245        if (factory == null) {
    246            _socketFactory_ = DEFAULT_SOCKET_FACTORY;
    247        } else {
    248            _socketFactory_ = factory;
    249        }
    250    }
    251
    252    /**
    253     * Set the default timeout in to use when opening a socket. After a call to open, the timeout for the socket is set using this value. This
    254     * method should be used prior to a call to {@link #open open()} and should not be confused with {@link #setSoTimeout setSoTimeout()} which operates on the
    255     * currently open socket. _timeout_ contains the new timeout value.
    256     *
    257     * @param timeout The timeout durations to use for the datagram socket connection.
    258     */
    259    public void setDefaultTimeout(final Duration timeout) {
    260        _timeout_ = Math.toIntExact(timeout.toMillis());
    261    }
    262
    263    /**
    264     * Set the default timeout in milliseconds to use when opening a socket. After a call to open, the timeout for the socket is set using this value. This
    265     * method should be used prior to a call to {@link #open open()} and should not be confused with {@link #setSoTimeout setSoTimeout()} which operates on the
    266     * currently open socket. _timeout_ contains the new timeout value.
    267     *
    268     * @param timeout The timeout in milliseconds to use for the datagram socket connection.
    269     * @deprecated Use {@link #setDefaultTimeout(Duration)}.
    270     */
    271    @Deprecated
    272    public void setDefaultTimeout(final int timeout) {
    273        _timeout_ = timeout;
    274    }
    275
    276    /**
    277     * Set the timeout duration of a currently open connection. Only call this method after a connection has been opened by {@link #open open()}.
    278     *
    279     * @param timeout The timeout in milliseconds to use for the currently open datagram socket connection.
    280     * @throws SocketException if an error setting the timeout.
    281     * @since 3.10.0
    282     */
    283    public void setSoTimeout(final Duration timeout) throws SocketException {
    284        checkOpen().setSoTimeout(Math.toIntExact(timeout.toMillis()));
    285    }
    286
    287    /**
    288     * Set the timeout in milliseconds of a currently open connection. Only call this method after a connection has been opened by {@link #open open()}.
    289     *
    290     * @param timeout The timeout in milliseconds to use for the currently open datagram socket connection.
    291     * @throws SocketException if an error setting the timeout.
    292     * @deprecated Use {@link #setSoTimeout(Duration)}.
    293     */
    294    @Deprecated
    295    public void setSoTimeout(final int timeout) throws SocketException {
    296        checkOpen().setSoTimeout(timeout);
    297    }
    298}