Jvm.kt

  1. package com.hexagonkt.core

  2. import com.hexagonkt.core.text.SNAKE_CASE
  3. import com.hexagonkt.core.text.parseOrNull
  4. import java.io.Console
  5. import java.net.InetAddress
  6. import java.nio.charset.Charset
  7. import java.time.ZoneId
  8. import java.util.*

  9. import kotlin.reflect.KClass

  10. /**
  11.  * Object with utilities to gather information about the running JVM.
  12.  */
  13. object Jvm {
  14.     private val systemSettingPattern: Regex by lazy { SNAKE_CASE }

  15.     /** Operating system name ('os.name' property). If `null` throws an exception. */
  16.     val os: String by lazy { os() }

  17.     /** Operating system type. */
  18.     val osKind: OsKind by lazy { osKind() }

  19.     /**
  20.      * JVM Console, if the program don't have a console (i.e.: input or output redirected), an
  21.      * exception is thrown.
  22.      */
  23.     val console: Console by lazy {
  24.         System.console() ?: error("Program doesn't have a console (I/O may be redirected)")
  25.     }

  26.     /** True if the program has a console (terminal, TTY, PTY...), false if I/O is piped. */
  27.     val isConsole: Boolean by lazy { System.console() != null }

  28.     /** Current JVM runtime. */
  29.     val runtime: Runtime by lazy { Runtime.getRuntime() }

  30.     /** Default timezone. */
  31.     val timeZone: TimeZone by lazy { TimeZone.getDefault() }

  32.     /** Default zone ID. */
  33.     val zoneId: ZoneId by lazy { timeZone.toZoneId() }

  34.     /** Default character set. */
  35.     val charset: Charset by lazy { Charset.defaultCharset() }

  36.     /** Default locale for this instance of the Java Virtual Machine. */
  37.     val locale: Locale by lazy { Locale.getDefault() }

  38.     /** The host name of the machine running this program. */
  39.     val hostName: String by lazy { InetAddress.getLocalHost().hostName }

  40.     /** The IP address of the machine running this program. */
  41.     val ip: String by lazy { InetAddress.getLocalHost().hostAddress }

  42.     /** Name of the JVM running this program. For example: OpenJDK 64-Bit Server VM. */
  43.     val name: String by lazy { System.getProperty("java.vm.name", "N/A") }

  44.     /** Java version aka language level. For example: 11 */
  45.     val version: String by lazy { System.getProperty("java.vm.specification.version", "N/A") }

  46.     /** Number of processors available to the Java virtual machine. */
  47.     val cpuCount: Int by lazy { runtime.availableProcessors() }

  48.     /** User locale consist of 2-letter language code, 2-letter country code and file encoding. */
  49.     val localeCode: String by lazy {
  50.         "%s_%s.%s".format(locale.language, locale.country, charset.name())
  51.     }

  52.     /**
  53.      * Amount of memory in kilobytes available to the JVM.
  54.      *
  55.      * @return Total amount of memory in kilobytes.
  56.      */
  57.     fun totalMemory(): String =
  58.         runtime.totalMemory().let { "%,d".format(it / 1024) }

  59.     /**
  60.      * Amount of used memory in kilobytes.
  61.      *
  62.      * @return Used memory in kilobytes.
  63.      */
  64.     fun usedMemory(): String =
  65.         (runtime.totalMemory() - runtime.freeMemory()).let { "%,d".format(it / 1024) }

  66.     /**
  67.      * Add a map to system properties, overriding entries if already set.
  68.      *
  69.      * @param settings Data to be added to system properties.
  70.      */
  71.     fun loadSystemSettings(settings: Map<String, String>) {
  72.         settings.entries.forEach { (k, v) ->
  73.             val matchPattern = k.matches(systemSettingPattern)
  74.             check(matchPattern) { "Property name must match $systemSettingPattern ($k)" }
  75.             System.setProperty(k, v)
  76.         }
  77.     }

  78.     /**
  79.      * Retrieve a setting by name by looking in OS environment variables first and in the JVM system
  80.      * properties if not found.
  81.      *
  82.      * @param type Type of the requested parameter. Supported types are: boolean, int, long, float,
  83.      *   double and string, throw an error if other type is supplied.
  84.      * @param name Name of the searched parameter, can not be blank.
  85.      * @return Value of the searched parameter in the requested type, `null` if the parameter is not
  86.      *   found on the OS environment variables or in JVM system properties.
  87.      */
  88.     fun <T: Any> systemSettingOrNull(type: KClass<T>, name: String): T? =
  89.         systemSettingRaw(name).parseOrNull(type)

  90.     fun <T: Any> systemSetting(type: KClass<T>, name: String): T =
  91.         systemSettingOrNull(type, name)
  92.             ?: error("Required '${type.simpleName}' system setting '$name' not found")

  93.     fun <T: Any> systemSetting(type: KClass<T>, name: String, defaultValue: T): T =
  94.         systemSettingOrNull(type, name) ?: defaultValue

  95.     /**
  96.      * Retrieve a flag (boolean parameter) by name by looking in OS environment variables first and
  97.      * in the JVM system properties if not found.
  98.      *
  99.      * @param name Name of the searched parameter, can not be blank.
  100.      * @return True if the parameter is found and its value is exactly 'true', false otherwise.
  101.      */
  102.     fun systemFlag(name: String): Boolean =
  103.         systemSettingOrNull(Boolean::class, name) ?: false

  104.     /**
  105.      * Utility method for retrieving a system setting, check [systemSettingOrNull] for details.
  106.      *
  107.      * @param T Type of the requested parameter. Supported types are: boolean, int, long, float,
  108.      *   double and string, throw an error if other type is supplied.
  109.      * @param name Name of the searched parameter, can not be blank.
  110.      * @return Value of the searched parameter in the requested type, `null` if the parameter is not
  111.      *   found on the OS environment variables or in JVM system properties.
  112.      */
  113.     inline fun <reified T: Any> systemSettingOrNull(name: String): T? =
  114.         systemSettingOrNull(T::class, name)

  115.     inline fun <reified T: Any> systemSetting(name: String): T =
  116.         systemSetting(T::class, name)

  117.     inline fun <reified T: Any> systemSetting(name: String, defaultValue: T): T =
  118.         systemSetting(T::class, name, defaultValue)

  119.     private fun systemSettingRaw(name: String): String? {
  120.         val correctName = name.matches(systemSettingPattern)
  121.         require(correctName) { "Setting name must match $systemSettingPattern" }
  122.         return System.getenv(name) ?: System.getenv(name.uppercase()) ?: System.getProperty(name)
  123.     }

  124.     /** Operating system name ('os.name' property). If `null` throws an exception. */
  125.     internal fun os(): String =
  126.         System.getProperty("os.name") ?: error("OS property ('os.name') not found")

  127.     /** Operating system type. */
  128.     internal fun osKind(): OsKind =
  129.         os().lowercase().let {
  130.             when {
  131.                 it.contains("win") -> OsKind.WINDOWS
  132.                 it.contains("mac") -> OsKind.MACOS
  133.                 it.contains("nux") -> OsKind.LINUX
  134.                 it.contains("nix") || it.contains("aix") -> OsKind.UNIX
  135.                 else -> error("Unsupported OS: ${os()}")
  136.             }
  137.         }
  138. }