Jvm.kt
package com.hexagonkt.core
import com.hexagonkt.core.text.SNAKE_CASE
import com.hexagonkt.core.text.parseOrNull
import java.io.Console
import java.net.InetAddress
import java.nio.charset.Charset
import java.time.ZoneId
import java.util.*
import kotlin.reflect.KClass
/**
* Object with utilities to gather information about the running JVM.
*/
object Jvm {
private val systemSettingPattern: Regex by lazy { SNAKE_CASE }
/** Operating system name ('os.name' property). If `null` throws an exception. */
val os: String by lazy { os() }
/** Operating system type. */
val osKind: OsKind by lazy { osKind() }
/**
* JVM Console, if the program don't have a console (i.e.: input or output redirected), an
* exception is thrown.
*/
val console: Console by lazy {
System.console() ?: error("Program doesn't have a console (I/O may be redirected)")
}
/** True if the program has a console (terminal, TTY, PTY...), false if I/O is piped. */
val isConsole: Boolean by lazy { System.console() != null }
/** Current JVM runtime. */
val runtime: Runtime by lazy { Runtime.getRuntime() }
/** Default timezone. */
val timeZone: TimeZone by lazy { TimeZone.getDefault() }
/** Default zone ID. */
val zoneId: ZoneId by lazy { timeZone.toZoneId() }
/** Default character set. */
val charset: Charset by lazy { Charset.defaultCharset() }
/** Default locale for this instance of the Java Virtual Machine. */
val locale: Locale by lazy { Locale.getDefault() }
/** The host name of the machine running this program. */
val hostName: String by lazy { InetAddress.getLocalHost().hostName }
/** The IP address of the machine running this program. */
val ip: String by lazy { InetAddress.getLocalHost().hostAddress }
/** Name of the JVM running this program. For example: OpenJDK 64-Bit Server VM. */
val name: String by lazy { System.getProperty("java.vm.name", "N/A") }
/** Java version aka language level. For example: 11 */
val version: String by lazy { System.getProperty("java.vm.specification.version", "N/A") }
/** Number of processors available to the Java virtual machine. */
val cpuCount: Int by lazy { runtime.availableProcessors() }
/** User locale consist of 2-letter language code, 2-letter country code and file encoding. */
val localeCode: String by lazy {
"%s_%s.%s".format(locale.language, locale.country, charset.name())
}
/**
* Amount of memory in kilobytes available to the JVM.
*
* @return Total amount of memory in kilobytes.
*/
fun totalMemory(): String =
runtime.totalMemory().let { "%,d".format(it / 1024) }
/**
* Amount of used memory in kilobytes.
*
* @return Used memory in kilobytes.
*/
fun usedMemory(): String =
(runtime.totalMemory() - runtime.freeMemory()).let { "%,d".format(it / 1024) }
/**
* Add a map to system properties, overriding entries if already set.
*
* @param settings Data to be added to system properties.
*/
fun loadSystemSettings(settings: Map<String, String>) {
settings.entries.forEach { (k, v) ->
val matchPattern = k.matches(systemSettingPattern)
check(matchPattern) { "Property name must match $systemSettingPattern ($k)" }
System.setProperty(k, v)
}
}
/**
* Retrieve a setting by name by looking in OS environment variables first and in the JVM system
* properties if not found.
*
* @param type Type of the requested parameter. Supported types are: boolean, int, long, float,
* double and string, throw an error if other type is supplied.
* @param name Name of the searched parameter, can not be blank.
* @return Value of the searched parameter in the requested type, `null` if the parameter is not
* found on the OS environment variables or in JVM system properties.
*/
fun <T: Any> systemSettingOrNull(type: KClass<T>, name: String): T? =
systemSettingRaw(name).parseOrNull(type)
fun <T: Any> systemSetting(type: KClass<T>, name: String): T =
systemSettingOrNull(type, name)
?: error("Required '${type.simpleName}' system setting '$name' not found")
fun <T: Any> systemSetting(type: KClass<T>, name: String, defaultValue: T): T =
systemSettingOrNull(type, name) ?: defaultValue
/**
* Retrieve a flag (boolean parameter) by name by looking in OS environment variables first and
* in the JVM system properties if not found.
*
* @param name Name of the searched parameter, can not be blank.
* @return True if the parameter is found and its value is exactly 'true', false otherwise.
*/
fun systemFlag(name: String): Boolean =
systemSettingOrNull(Boolean::class, name) ?: false
/**
* Utility method for retrieving a system setting, check [systemSettingOrNull] for details.
*
* @param T Type of the requested parameter. Supported types are: boolean, int, long, float,
* double and string, throw an error if other type is supplied.
* @param name Name of the searched parameter, can not be blank.
* @return Value of the searched parameter in the requested type, `null` if the parameter is not
* found on the OS environment variables or in JVM system properties.
*/
inline fun <reified T: Any> systemSettingOrNull(name: String): T? =
systemSettingOrNull(T::class, name)
inline fun <reified T: Any> systemSetting(name: String): T =
systemSetting(T::class, name)
inline fun <reified T: Any> systemSetting(name: String, defaultValue: T): T =
systemSetting(T::class, name, defaultValue)
private fun systemSettingRaw(name: String): String? {
val correctName = name.matches(systemSettingPattern)
require(correctName) { "Setting name must match $systemSettingPattern" }
return System.getenv(name) ?: System.getenv(name.uppercase()) ?: System.getProperty(name)
}
/** Operating system name ('os.name' property). If `null` throws an exception. */
internal fun os(): String =
System.getProperty("os.name") ?: error("OS property ('os.name') not found")
/** Operating system type. */
internal fun osKind(): OsKind =
os().lowercase().let {
when {
it.contains("win") -> OsKind.WINDOWS
it.contains("mac") -> OsKind.MACOS
it.contains("nux") -> OsKind.LINUX
it.contains("nix") || it.contains("aix") -> OsKind.UNIX
else -> error("Unsupported OS: ${os()}")
}
}
}