Platform.kt

  1. package com.hexagontk.core

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

  8. import kotlin.reflect.KClass

  9. /**
  10.  * Object with utilities to gather information about the running platform.
  11.  */
  12. object Platform {
  13.     private val systemSettingPattern: Regex by lazy { Regex("[_A-Za-z]+[_A-Za-z0-9]*") }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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