RandomPortUtils.java

package fr.sii.ogham.testing.util;

import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.util.SortedSet;

import javax.net.ServerSocketFactory;

import fr.sii.ogham.testing.util.port.DefaultPortFinder;
import fr.sii.ogham.testing.util.port.PortFinder;

/**
 * Simple utility methods for finding available ports on {@code localhost}.
 *
 * <p>
 * Within this class, a TCP port refers to a port for a {@link ServerSocket};
 * whereas, a UDP port refers to a port for a {@link DatagramSocket}.
 * 
 * <strong>NOTE:</strong> This code has been borrowed from Spring Framework.
 * 
 * @see "https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/util/SocketUtils.java"
 */
public final class RandomPortUtils {

	/**
	 * The default minimum value for port ranges used when finding an available
	 * socket port.
	 */
	public static final int PORT_RANGE_MIN = 1024;

	/**
	 * The default maximum value for port ranges used when finding an available
	 * socket port.
	 */
	public static final int PORT_RANGE_MAX = 65535;

	/**
	 * Find an available TCP port randomly selected from the range
	 * [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}].
	 * 
	 * @return an available TCP port number
	 * @throws IllegalStateException
	 *             if no available port could be found
	 */
	public static int findAvailableTcpPort() {
		return findAvailableTcpPort(PORT_RANGE_MIN);
	}

	/**
	 * Find an available TCP port randomly selected from the range
	 * [{@code minPort}, {@value #PORT_RANGE_MAX}].
	 * 
	 * @param minPort
	 *            the minimum port number
	 * @return an available TCP port number
	 * @throws IllegalStateException
	 *             if no available port could be found
	 */
	public static int findAvailableTcpPort(int minPort) {
		return findAvailableTcpPort(minPort, PORT_RANGE_MAX);
	}

	/**
	 * Find an available TCP port randomly selected from the range
	 * [{@code minPort}, {@code maxPort}].
	 * 
	 * @param minPort
	 *            the minimum port number
	 * @param maxPort
	 *            the maximum port number
	 * @return an available TCP port number
	 * @throws IllegalStateException
	 *             if no available port could be found
	 */
	public static int findAvailableTcpPort(int minPort, int maxPort) {
		return SocketType.TCP.findAvailablePort(minPort, maxPort);
	}

	/**
	 * Find the requested number of available TCP ports, each randomly selected
	 * from the range [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}].
	 * 
	 * @param numRequested
	 *            the number of available ports to find
	 * @return a sorted set of available TCP port numbers
	 * @throws IllegalStateException
	 *             if the requested number of available ports could not be found
	 */
	public static SortedSet<Integer> findAvailableTcpPorts(int numRequested) {
		return findAvailableTcpPorts(numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX);
	}

	/**
	 * Find the requested number of available TCP ports, each randomly selected
	 * from the range [{@code minPort}, {@code maxPort}].
	 * 
	 * @param numRequested
	 *            the number of available ports to find
	 * @param minPort
	 *            the minimum port number
	 * @param maxPort
	 *            the maximum port number
	 * @return a sorted set of available TCP port numbers
	 * @throws IllegalStateException
	 *             if the requested number of available ports could not be found
	 */
	public static SortedSet<Integer> findAvailableTcpPorts(int numRequested, int minPort, int maxPort) {
		return SocketType.TCP.findAvailablePorts(numRequested, minPort, maxPort);
	}

	/**
	 * Find an available UDP port randomly selected from the range
	 * [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}].
	 * 
	 * @return an available UDP port number
	 * @throws IllegalStateException
	 *             if no available port could be found
	 */
	public static int findAvailableUdpPort() {
		return findAvailableUdpPort(PORT_RANGE_MIN);
	}

	/**
	 * Find an available UDP port randomly selected from the range
	 * [{@code minPort}, {@value #PORT_RANGE_MAX}].
	 * 
	 * @param minPort
	 *            the minimum port number
	 * @return an available UDP port number
	 * @throws IllegalStateException
	 *             if no available port could be found
	 */
	public static int findAvailableUdpPort(int minPort) {
		return findAvailableUdpPort(minPort, PORT_RANGE_MAX);
	}

	/**
	 * Find an available UDP port randomly selected from the range
	 * [{@code minPort}, {@code maxPort}].
	 * 
	 * @param minPort
	 *            the minimum port number
	 * @param maxPort
	 *            the maximum port number
	 * @return an available UDP port number
	 * @throws IllegalStateException
	 *             if no available port could be found
	 */
	public static int findAvailableUdpPort(int minPort, int maxPort) {
		return SocketType.UDP.findAvailablePort(minPort, maxPort);
	}

	/**
	 * Find the requested number of available UDP ports, each randomly selected
	 * from the range [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}].
	 * 
	 * @param numRequested
	 *            the number of available ports to find
	 * @return a sorted set of available UDP port numbers
	 * @throws IllegalStateException
	 *             if the requested number of available ports could not be found
	 */
	public static SortedSet<Integer> findAvailableUdpPorts(int numRequested) {
		return findAvailableUdpPorts(numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX);
	}

	/**
	 * Find the requested number of available UDP ports, each randomly selected
	 * from the range [{@code minPort}, {@code maxPort}].
	 * 
	 * @param numRequested
	 *            the number of available ports to find
	 * @param minPort
	 *            the minimum port number
	 * @param maxPort
	 *            the maximum port number
	 * @return a sorted set of available UDP port numbers
	 * @throws IllegalStateException
	 *             if the requested number of available ports could not be found
	 */
	public static SortedSet<Integer> findAvailableUdpPorts(int numRequested, int minPort, int maxPort) {
		return SocketType.UDP.findAvailablePorts(numRequested, minPort, maxPort);
	}

	@SuppressWarnings("squid:IndentationCheck")
	private enum SocketType implements PortFinder {

		TCP {
			@Override
			protected boolean isPortAvailable(int port) {
				try {
					ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(port, 1, InetAddress.getByName("localhost"));
					serverSocket.close();
					return true;
				} catch (Exception ex) {
					return false;
				}
			}
		},

		UDP {
			@Override
			protected boolean isPortAvailable(int port) {
				try {
					DatagramSocket socket = new DatagramSocket(port, InetAddress.getByName("localhost"));
					socket.close();
					return true;
				} catch (Exception ex) {
					return false;
				}
			}
		};
		
		private PortFinder delegate;
		
		SocketType() {
			this.delegate = new DefaultPortFinder(name(), this::isPortAvailable);
		}

		protected abstract boolean isPortAvailable(int port);
		
		/**
		 * Find an available port for this {@code SocketType}, randomly selected
		 * from the range [{@code minPort}, {@code maxPort}].
		 * 
		 * @param minPort
		 *            the minimum port number
		 * @param maxPort
		 *            the maximum port number
		 * @return an available port number for this socket type
		 * @throws IllegalStateException
		 *             if no available port could be found
		 */
		@Override
		public int findAvailablePort(int minPort, int maxPort) {
			return delegate.findAvailablePort(minPort, maxPort);
		}
		
		/**
		 * Find the requested number of available ports for this
		 * {@code SocketType}, each randomly selected from the range
		 * [{@code minPort}, {@code maxPort}].
		 * 
		 * @param numRequested
		 *            the number of available ports to find
		 * @param minPort
		 *            the minimum port number
		 * @param maxPort
		 *            the maximum port number
		 * @return a sorted set of available port numbers for this socket type
		 * @throws IllegalStateException
		 *             if the requested number of available ports could not be
		 *             found
		 */
		@Override
		public SortedSet<Integer> findAvailablePorts(int numRequested, int minPort, int maxPort) {
			return delegate.findAvailablePorts(numRequested, minPort, maxPort);
		}
	}

	private RandomPortUtils() {
		super();
	}

}