View Javadoc
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 }