1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.net;
19
20 import java.net.DatagramSocket;
21 import java.net.InetAddress;
22 import java.net.SocketException;
23 import java.nio.charset.Charset;
24 import java.time.Duration;
25 import java.util.Objects;
26
27 /**
28 * The DatagramSocketClient provides the basic operations that are required of client objects accessing datagram sockets. It is meant to be subclassed to avoid
29 * 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
30 * {@link #setDatagramSocketFactory setDatagramSocketFactory } method, which allows you to control the type of DatagramSocket the DatagramSocketClient creates
31 * for network communications. This is especially useful for adding things like proxy support as well as better support for applets. For example, you could
32 * create a {@link org.apache.commons.net.DatagramSocketFactory} that requests browser security capabilities before creating a socket. All classes derived from
33 * DatagramSocketClient should use the {@link #_socketFactory_ _socketFactory_ } member variable to create DatagramSocket instances rather than instantiating
34 * 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
35 * substituting his own SocketFactory.
36 *
37 *
38 * @see DatagramSocketFactory
39 */
40
41 public abstract class DatagramSocketClient implements AutoCloseable {
42 /**
43 * The default DatagramSocketFactory shared by all DatagramSocketClient instances.
44 */
45 private static final DatagramSocketFactory DEFAULT_SOCKET_FACTORY = new DefaultDatagramSocketFactory();
46
47 /**
48 * Charset to use for byte IO.
49 */
50 private Charset charset = Charset.defaultCharset();
51
52 /** The timeout to use after opening a socket. */
53 protected int _timeout_;
54
55 /** The datagram socket used for the connection. */
56 protected DatagramSocket _socket_;
57
58 /**
59 * A status variable indicating if the client's socket is currently open.
60 */
61 protected boolean _isOpen_;
62
63 /** The datagram socket's DatagramSocketFactory. */
64 protected DatagramSocketFactory _socketFactory_ = DEFAULT_SOCKET_FACTORY;
65
66 /**
67 * Default constructor for DatagramSocketClient. Initializes _socket_ to null, _timeout_ to 0, and _isOpen_ to false.
68 */
69 public DatagramSocketClient() {
70 }
71
72 /**
73 * Gets the non-null DatagramSocket or throws {@link NullPointerException}.
74 *
75 * <p>
76 * This method does not allocate resources.
77 * </p>
78 *
79 * @return the non-null DatagramSocket.
80 * @since 3.10.0
81 */
82 protected DatagramSocket checkOpen() {
83 return Objects.requireNonNull(_socket_, "DatagramSocket");
84 }
85
86 /**
87 * 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
88 * {@link #open open() } again. _isOpen_ is set to false and _socket_ is set to null.
89 */
90 @Override
91 public void close() {
92 if (_socket_ != null) {
93 _socket_.close();
94 }
95 _socket_ = null;
96 _isOpen_ = false;
97 }
98
99 /**
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 }