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}