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