Class Utilities

java.lang.Object
com.soklet.Utilities

@ThreadSafe public final class Utilities extends Object
A non-instantiable collection of utility methods.
Author:
Mark Allen
  • Method Details

    • virtualThreadsAvailable

      Does the platform runtime support virtual threads (either Java 19 and 20 w/preview enabled or Java 21+)?
      Returns:
      true if the runtime supports virtual threads, false otherwise
    • createVirtualThreadsNewThreadPerTaskExecutor

      Provides a virtual-thread-per-task executor service if supported by the runtime.

      In order to support Soklet users who are not yet ready to enable virtual threads (those not running either Java 19 and 20 w/preview enabled or Java 21+), we compile Soklet with a source level < 19 and avoid any hard references to virtual threads by dynamically creating our executor service via MethodHandle references.

      You should not call this method if virtualThreadsAvailable() is false.

       // This method is effectively equivalent to this code
      return Executors.newThreadPerTaskExecutor(
        Thread.ofVirtual()
         .name(threadNamePrefix)
         .uncaughtExceptionHandler(uncaughtExceptionHandler)
         .factory()
      );
      Parameters:
      threadNamePrefix - thread name prefix for the virtual thread factory builder
      uncaughtExceptionHandler - uncaught exception handler for the virtual thread factory builder
      Returns:
      a virtual-thread-per-task executor service
      Throws:
      IllegalStateException - if the runtime environment does not support virtual threads
    • emptyByteArray

      @Nonnull public static byte[] emptyByteArray()
      Returns a shared zero-length byte[] instance.

      Useful as a sentinel when you need a non-null byte array but have no content.

      Returns:
      a zero-length byte array (never null)
    • extractQueryParametersFromQuery

      Parses an application/x-www-form-urlencoded query string into a multimap of names to values.

      Decodes percent-escapes and '+' as space using UTF-8. Pairs missing a name or value are ignored. Multiple occurrences of the same name are collected into a Set in insertion order (duplicates are de-duplicated).

      Parameters:
      query - a raw query string such as "a=1&b=2&b=3" (must be non-null)
      Returns:
      a map of parameter names to their distinct values, preserving first-seen name order; empty if none
      See Also:
    • extractQueryParametersFromUrl

      Extracts query parameters from a URL (or URI string) into a multimap of names to values.

      If the input is not a valid URI, an empty map is returned. The raw query is split on '&' into name/value pairs, values are split on the first '=', and both name and value are UTF-8 decoded (percent-escapes and '+' → space). Blank pairs and pairs missing either name or value are ignored. Multiple occurrences of the same name are collected into a Set in insertion order (duplicates are de-duplicated).

      Parameters:
      url - an absolute or relative URL/URI string (must be non-null)
      Returns:
      a map of parameter names to their distinct values, preserving first-seen name order; empty if none/invalid
    • extractCookiesFromHeaders

      Parses Cookie request headers into a map of cookie names to values.

      Header name matching is case-insensitive ("Cookie" vs "cookie"), but cookie names are case-sensitive. Values are parsed per the following liberal rules:

      • Components are split on ';' unless inside a quoted string.
      • Quoted values have surrounding quotes removed and common backslash escapes unescaped.
      • Percent-escapes are decoded as UTF-8. '+' is not treated specially.
      Multiple occurrences of the same cookie name are collected into a Set in insertion order.
      Parameters:
      headers - request headers as a multimap of header name to values (must be non-null)
      Returns:
      a map of cookie name to distinct values; empty if no valid cookies are present
    • normalizedPathForUrl

      Normalizes a URL or path into a canonical request path.

      Behavior:

      • If input starts with http:// or https://, the path portion is extracted.
      • Ensures the result begins with '/'.
      • Removes any trailing '/' (except for the root path '/').
      • Strips any query string.
      • Applies aggressive trimming of Unicode whitespace.
      Parameters:
      url - a URL or path to normalize (must be non-null)
      Returns:
      the normalized path (never null); "/" for empty input
    • localesFromAcceptLanguageHeaderValue

      @Nonnull public static List<Locale> localesFromAcceptLanguageHeaderValue(@Nonnull String acceptLanguageHeaderValue)
      Parses an Accept-Language header value into a best-effort ordered list of Locales.

      Quality weights are honored by Locale.LanguageRange.parse(String); results are then mapped to available JVM locales. Unknown or unavailable language ranges are skipped. On parse failure, an empty list is returned.

      Parameters:
      acceptLanguageHeaderValue - the raw header value (must be non-null)
      Returns:
      locales in descending preference order; empty if none could be resolved
    • extractClientUrlPrefixFromHeaders

      Best-effort attempt to determine a client's URL prefix by examining request headers.

      A URL prefix in this context is defined as <scheme>://host<:optional port>, but no path or query components.

      Soklet is generally the "last hop" behind a load balancer/reverse proxy and does get accessed directly by clients.

      Normally a load balancer/reverse proxy/other upstream proxies will provide information about the true source of the request through headers like the following:

      • Host
      • Forwarded
      • Origin
      • X-Forwarded-Proto
      • X-Forwarded-Protocol
      • X-Url-Scheme
      • Front-End-Https
      • X-Forwarded-Ssl
      • X-Forwarded-Host
      • X-Forwarded-Port

      This method may take these and other headers into account when determining URL prefix.

      For example, the following would be legal URL prefixes returned from this method:

      • https://www.soklet.com
      • http://www.fake.com:1234

      The following would NOT be legal URL prefixes:

      • www.soklet.com (missing protocol)
      • https://www.soklet.com/ (trailing slash)
      • https://www.soklet.com/test (trailing slash, path)
      • https://www.soklet.com/test?abc=1234 (trailing slash, path, query)
      Parameters:
      headers - HTTP request headers
      Returns:
      the URL prefix, or Optional.empty() if it could not be determined
    • extractContentTypeFromHeaders

      Extracts the media type (without parameters) from the first Content-Type header.

      For example, "text/html; charset=utf-8""text/html".

      Parameters:
      headers - request/response headers (must be non-null)
      Returns:
      the media type if present; otherwise Optional.empty()
      See Also:
    • extractContentTypeFromHeaderValue

      @Nonnull public static Optional<String> extractContentTypeFromHeaderValue(@Nullable String contentTypeHeaderValue)
      Extracts the media type (without parameters) from a Content-Type header value.

      For example, "application/json; charset=utf-8""application/json".

      Parameters:
      contentTypeHeaderValue - the raw header value; may be null or blank
      Returns:
      the media type if present; otherwise Optional.empty()
    • extractCharsetFromHeaders

      Extracts the Charset from the first Content-Type header, if present and valid.

      Tolerates additional parameters and arbitrary whitespace. Invalid or unknown charset tokens yield Optional.empty().

      Parameters:
      headers - request/response headers (must be non-null)
      Returns:
      the charset declared by the header; otherwise Optional.empty()
      See Also:
    • extractCharsetFromHeaderValue

      @Nonnull public static Optional<Charset> extractCharsetFromHeaderValue(@Nullable String contentTypeHeaderValue)
      Extracts the charset=... parameter from a Content-Type header value.

      Parsing is forgiving: parameters may appear in any order and with arbitrary spacing. If a charset is found, it is validated via Charset.forName(String); invalid names result in Optional.empty().

      Parameters:
      contentTypeHeaderValue - the raw header value; may be null or blank
      Returns:
      the resolved charset if present and valid; otherwise Optional.empty()
    • trimAggressively

      A "stronger" version of String.trim() which discards any kind of whitespace or invisible separator.

      In a web environment with user-supplied inputs, this is the behavior we want the vast majority of the time. For example, users copy-paste URLs from Microsoft Word or Outlook and it's easy to accidentally include a U+202F "Narrow No-Break Space (NNBSP)" character at the end, which might break parsing.

      See https://www.compart.com/en/unicode/U+202F for details.

      Parameters:
      string - the string to trim
      Returns:
      the trimmed string, or null if the input string is null or the trimmed representation is of length 0
    • trimAggressivelyToNull

      Aggressively trims Unicode whitespace from the given string and returns null if the result is empty.

      See trimAggressively(String) for details on which code points are removed.

      Parameters:
      string - the input string; may be null
      Returns:
      a trimmed, non-empty string; or null if input was null or trimmed to empty
    • trimAggressivelyToEmpty

      Aggressively trims Unicode whitespace from the given string and returns "" if the input is null.

      See trimAggressively(String) for details on which code points are removed.

      Parameters:
      string - the input string; may be null
      Returns:
      a trimmed string (never null); "" if input was null