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}