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}