java.lang.Object
com.clumd.projects.java_common_utils.base_enhancements.PortableSocket
All Implemented Interfaces:
AutoCloseable

public class PortableSocket extends Object implements AutoCloseable
This Socket wrapper serves to accumulate some convenience methods for dealing with sockets, such as setting up ObjectStreams, setting timeouts and preparing customised ClassLoaders for interpreting incoming objects. It is also called 'Portable' as THIS is the object reference which should be passed around to represent a socket endpoint, rather than the socket itself and create multiple input/output streams on.

One other extremely useful feature it provides is a PORTABLE method of determining a connection's reachability.

As per the Java documentation and countless stackoverflow posts, the baked in InetAddress.isReachable(int) will only make best-effort to perform a regular ICMP PING request. However, certain operating systems may only allow these calls if the JVM is running with ROOT/ADMIN privileges. This implementation (portableIsReachable(String, int)) seeks to improve this, by going a few steps further to determine reachability such as actually trying to construct a Socket to the peer on a known, provided, port.

  • Constructor Details

    • PortableSocket

      public PortableSocket(@NonNull @NonNull Socket socket, Serializable... requiredSocketStreamHeaderContent) throws IOException
      This constructor accepts a *connected* Socket, and potentially, a collection of objects which will be written and expected to be read as a stream header. This allows both sides of a PortableSocket to match each other reliably. This constructor will create an OutputStream for this socket, will set a Medium timeout, but will NOT immediately create an InputStream. This is to allow a caller to potentially provide a custom URL ClassLoader through initialiseInputStreamWithComponentLoader(URLClassLoader).
      Parameters:
      socket - The already connected low level Socket. If a socket is provided but is not connected, then you can't use a portable socket yet as, to cope with this, involves having to then keep track of all the is initialised and connected etc. Basically I'm just a bit lazy, but I don't think it makes sense for this class.
      requiredSocketStreamHeaderContent - Variadic args for what to write down the socket on connection, and expect to read from the socket on connection. Passing nothing and treating this constructor as if this variadic did not exist, will leave the DEFAULT (In/Out)putStream header behaviour in place. Passing a type-casted (Object[]) null, will disable any read/write operations within the streams initialisations. Passing any other variadic data, will write those objects in order and expect to read the same from the other side on initialisation.
      Throws:
      IOException - This can be thrown if there was an issue creating the output streams, or defaulting the socket operation timeouts.
    • PortableSocket

      public PortableSocket(@NonNull @NonNull String hostname, int port, Serializable... requiredSocketStreamHeaderContent) throws IOException
      This constructor accepts a hostname and port which we would like to connect to, and potentially, a collection of objects which will be written and expected to be read as a stream header. This allows both sides of a PortableSocket to match each other reliably. If this constructor fails to produce a successful connection to the hostname and port provided and exception will be thrown. This constructor will always attempt to connect with a medium timeout, create an OutputStream for this socket and set a Medium timeout on future operations, but will NOT immediately create an InputStream. This is to allow a caller to potentially provide a custom URL ClassLoader through initialiseInputStreamWithComponentLoader(URLClassLoader).
      Parameters:
      hostname - The network hostname or IP of the peer we would like to connect to.
      port - The port number we would like to initialise a connection on.
      requiredSocketStreamHeaderContent - Variadic args for what to write down the socket on connection, and expect to read from the socket on connection. Passing nothing and treating this constructor as if this variadic did not exist, will leave the DEFAULT (In/Out)putStream header behaviour in place. Passing a type-casted (Object[]) null, will disable any read/write operations within the streams initialisations. Passing any other variadic data, will write those objects in order and expect to read the same from the other side on initialisation.
      Throws:
      IOException - This can be thrown if there was an issue creating the output streams, or defaulting the socket operation timeouts.
  • Method Details

    • portableIsReachable

      public static boolean portableIsReachable(@NonNull @NonNull String hostname, int port) throws IOException
      This method should act as an enhanced and more reliable version of Java's built in InetAddress.isReachable(int). This is because - as is widely documented - in the Java documentation and countless stackoverflow posts, the baked in InetAddress.isReachable(int) will only make best-effort to perform a regular ICMP PING request. However, certain operating systems may only allow these calls if the JVM is running with ROOT/ADMIN privileges, which on most Windows and MacOS systems it won't be by default.

      This implementation goes the step further by first calling this InetAddress.isReachable(int), but if it fails, actually attempting to create the connection - which follows a different workflow in the JVM network stack regarding the permissions it asks for, (I.e. NOT asking the system to perform ICMP control operations). This method of checking can validate both address resolution, as well as actual route traversal to the remote host.
      Parameters:
      hostname - The hostname of the host we would like to check connectivity to.
      port - The port on that hostname which we would like to check connectivity to.
      Returns:
      True if the host is reachable, False otherwise.
      Throws:
      IOException - Thrown if there was an error setting up the Sockets, a timeout occurred, or the host was otherwise unreachable.
    • isLocalHostname

      public static boolean isLocalHostname(@NonNull @NonNull String hostname) throws IOException
      Used to determine whether a given hostname is local to the system which is currently running this code.
      Parameters:
      hostname - The String to check whether it is a locally addressable hostname
      Returns:
      True if the String provided is a local hostname, False otherwise.
      Throws:
      IOException - Thrown if there was some networking stack issue in determining whether this hostname is local, or where the string provided could never be a valid hostname.
    • initialiseInputStreamWithComponentLoader

      public ObjectInputStream initialiseInputStreamWithComponentLoader(@NonNull @NonNull URLClassLoader componentLoaderForInputStream) throws IOException
      Used to initialise the InputStream for this PortableSocket using a custom provided URLClassLoader, such that it can interpret the kinds of new Objects which this PortableSocket will be used to receive.

      This method should only ever be called once per PortableSocket, and must be called before any calls to getInputStream(), as that will attempt default initialisation (without your custom URL Classloader) if the Stream is not already initialised. If this method is being called for the first time initialisation, then any potential Stream Header information provided to the Constructor of this PortableSocket, will attempt to be READ on initialisation before successfully returning the Stream back to the caller.
      Parameters:
      componentLoaderForInputStream - The URLClassLoader which the InputStream for this PortableSocket should use when parsing data back into Objects.
      Returns:
      The successfully initialised InputStream ready to be read from.
      Throws:
      IOException - Thrown if the Stream has already been initialised elsewhere, or if any potential Stream Header information was not read as expected.
    • getOutputStream

      public ObjectOutputStream getOutputStream() throws IOException
      Used to acquire the reference to this Portable Socket's OUTPUT stream. Only one output stream is created per PortableSocket.
      Returns:
      The OutputStream associated with this PortableSocket.
      Throws:
      IOException - Thrown if this was the first time this method was called since constructing the Portable Socket, and there was an error setting up the OutputStream - such as the Socket being immediately closed, or being unable to write an uninterrupted Stream Header to the remote end of the Socket.
    • getInputStream

      public ObjectInputStream getInputStream() throws IOException
      Used to acquire the reference to this Portable Socket's INPUT stream. Only one input stream is created per PortableSocket.
      Returns:
      The InputStream associated with this PortableSocket.
      Throws:
      IOException - Thrown if this was the first time this method was called since constructing the Portable Socket, and there was an error setting up the InputStream - such as the Socket being immediately closed, or reading a Corrupt Stream Header from the remote end of the Socket.
    • isClosed

      public boolean isClosed()
      Simple pass-through method to the underlying Socket.isClosed().
      Returns:
      Whether the socket this PortableSocket is built on, reports itself as closed.
    • close

      public void close()
      Specified by:
      close in interface AutoCloseable
    • setFastTimeout

      public int setFastTimeout() throws SocketException
      Calls through to Socket.setSoTimeout(int) with a default 'short' value.
      Returns:
      The previous value which would have been returned by Socket.getSoTimeout() before calling this method.
      Throws:
      SocketException - Can be thrown for example if the Socket you are trying to set timeouts on, is closed.
    • setMediumTimeout

      public int setMediumTimeout() throws SocketException
      Calls through to Socket.setSoTimeout(int) with a default 'medium' value.
      Returns:
      The previous value which would have been returned by Socket.getSoTimeout() before calling this method.
      Throws:
      SocketException - Can be thrown for example if the Socket you are trying to set timeouts on, is closed.
    • setLongTimeout

      public int setLongTimeout() throws SocketException
      Calls through to Socket.setSoTimeout(int) with a default 'long' value.
      Returns:
      The previous value which would have been returned by Socket.getSoTimeout() before calling this method.
      Throws:
      SocketException - Can be thrown for example if the Socket you are trying to set timeouts on, is closed.
    • setOneSecondTimeout

      public int setOneSecondTimeout() throws SocketException
      Calls through to Socket.setSoTimeout(int) with a default 'short' value.
      Returns:
      The previous value which would have been returned by Socket.getSoTimeout() before calling this method.
      Throws:
      SocketException - Can be thrown for example if the Socket you are trying to set timeouts on, is closed.
    • setTwoSecondTimeout

      public int setTwoSecondTimeout() throws SocketException
      Calls through to Socket.setSoTimeout(int) with a default 'short' value.
      Returns:
      The previous value which would have been returned by Socket.getSoTimeout() before calling this method.
      Throws:
      SocketException - Can be thrown for example if the Socket you are trying to set timeouts on, is closed.
    • setThreeSecondTimeout

      public int setThreeSecondTimeout() throws SocketException
      Calls through to Socket.setSoTimeout(int) with a default 'medium' value.
      Returns:
      The previous value which would have been returned by Socket.getSoTimeout() before calling this method.
      Throws:
      SocketException - Can be thrown for example if the Socket you are trying to set timeouts on, is closed.
    • setFiveSecondTimeout

      public int setFiveSecondTimeout() throws SocketException
      Calls through to Socket.setSoTimeout(int) with a default 'medium' value.
      Returns:
      The previous value which would have been returned by Socket.getSoTimeout() before calling this method.
      Throws:
      SocketException - Can be thrown for example if the Socket you are trying to set timeouts on, is closed.
    • setTenSecondTimeout

      public int setTenSecondTimeout() throws SocketException
      Calls through to Socket.setSoTimeout(int) with a default 'long' value.
      Returns:
      The previous value which would have been returned by Socket.getSoTimeout() before calling this method.
      Throws:
      SocketException - Can be thrown for example if the Socket you are trying to set timeouts on, is closed.
    • setThirtySecondTimeout

      public int setThirtySecondTimeout() throws SocketException
      Calls through to Socket.setSoTimeout(int) with a default 'long' value.
      Returns:
      The previous value which would have been returned by Socket.getSoTimeout() before calling this method.
      Throws:
      SocketException - Can be thrown for example if the Socket you are trying to set timeouts on, is closed.
    • setNoTimeout

      public int setNoTimeout() throws SocketException
      Calls through to Socket.setSoTimeout(int) with a value of 0, which is interpreted as limitless.
      Returns:
      The previous value which would have been returned by Socket.getSoTimeout() before calling this method.
      Throws:
      SocketException - Can be thrown for example if the Socket you are trying to set timeouts on, is closed.
    • setCustomTimeoutInMs

      public int setCustomTimeoutInMs(int timeoutInMilliseconds) throws SocketException
      Calls through to Socket.setSoTimeout(int) with a custom provided value.
      Parameters:
      timeoutInMilliseconds - The amount of milliseconds which should be allowed to pass before read operations on this Socket's streams should throw timeout exceptions.
      Returns:
      The previous value which would have been returned by Socket.getSoTimeout() before calling this method.
      Throws:
      SocketException - Can be thrown for example if the Socket you are trying to set timeouts on, is closed.
    • getSocket

      public Socket getSocket()