All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dorkbox.os.OS.kt Maven / Gradle / Ivy

/*
 * Copyright 2010 dorkbox, llc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
@file:Suppress("unused")

package dorkbox.os

import java.awt.Color
import java.io.File
import java.security.AccessController
import java.security.PrivilegedAction
import java.util.*

object OS {
    // make the default unix
    @kotlin.jvm.JvmField
    val LINE_SEPARATOR = getProperty("line.separator", "\n")!!

    const val LINE_SEPARATOR_UNIX = "\n"
    const val LINE_SEPARATOR_WINDOWS = "\r\n"

    @kotlin.jvm.JvmField
    val TEMP_DIR = File(getProperty("java.io.tmpdir", "temp")!!).absoluteFile

    /**
     * The currently running MAJOR java version as a NUMBER. For example, "Java version 1.7u45", and converts it into 7, uses JEP 223 for java > 9
     */
    @kotlin.jvm.JvmField
    val javaVersion = _getJavaVersion()
    private var osType: OSType? = null

    /**
     * Returns the *ORIGINAL* system time zone, before (*IF*) it was changed to UTC
     */
    val originalTimeZone = TimeZone.getDefault().id

    init {
        if (!TEMP_DIR.isDirectory) {
            // create the temp dir if necessary because the TEMP dir doesn't exist.
            TEMP_DIR.mkdirs()
        }
        var osName = getProperty("os.name", "")!!
        var osArch = getProperty("os.arch", "")!!

        osName = osName.lowercase()
        osArch = osArch.lowercase()
        if (osName.startsWith("linux")) {
            // best way to determine if it's android.
            // Sometimes java binaries include Android classes on the classpath, even if it isn't actually Android, so we check the VM
            val value = getProperty("java.vm.name", "")
            val isAndroid = "Dalvik" == value
            if (isAndroid) {
                // android check from https://stackoverflow.com/questions/14859954/android-os-arch-output-for-arm-mips-x86
                when (osArch) {
                    "armeabi" -> {
                        // really old/low-end non-hf 32bit cpu
                        osType = OSType.AndroidArm56
                    }
                    "armeabi-v7a" -> {
                        // 32bit hf cpu
                        osType = OSType.AndroidArm7
                    }
                    "arm64-v8a" -> {
                        // 64bit hf cpu
                        osType = OSType.AndroidArm8
                    }
                    "x86" -> {
                        // 32bit x86 (usually emulator)
                        osType = OSType.AndroidX86
                    }
                    "x86_64" -> {
                        // 64bit x86 (usually emulator)
                        osType = OSType.AndroidX86_64
                    }
                    "mips" -> {
                        // 32bit mips
                        osType = OSType.AndroidMips
                    }
                    "mips64" -> {
                        // 64bit mips
                        osType = OSType.AndroidMips64
                    }
                    else -> {
                        // who knows?
                        osType = null
                    }
                }
            } else {
                when {
                    osArch == "amd64" -> {
                        // normal linux 32/64/arm32/arm64
                        osType = OSType.Linux64
                    }
                    osArch.startsWith("arm") -> {
                        if (osArch.contains("v8")) {
                            OSType.LinuxArm64
                        } else {
                            OSType.LinuxArm32
                        }
                    }
                    else -> {
                        OSType.Linux32
                    }
                }
            }
        } else if (osName.startsWith("windows")) {
            osType = if ("amd64" == osArch) {
                OSType.Windows64
            } else {
                OSType.Windows32
            }
        } else if (osName.startsWith("mac") || osName.startsWith("darwin")) {
            osType = if ("x86_64" == osArch) {
                OSType.MacOsX64
            } else {
                OSType.MacOsX32
            }
        } else if (osName.startsWith("freebsd") || osName.contains("nix") || osName.contains("nux") || osName.startsWith(
                "aix"
            )
        ) {
            osType = when (osArch) {
                "x86", "i386" -> {
                    OSType.Unix32
                }
                "arm" -> {
                    OSType.UnixArm
                }
                else -> {
                    OSType.Unix64
                }
            }
        } else if (osName.startsWith("solaris") || osName.startsWith("sunos")) {
            osType = OSType.Solaris
        } else {
            osType = null
        }

        /*
         * By default, the timer resolution on Windows ARE NOT high-resolution (16ms vs 1ms)
         *
         * 'Thread.sleep(1)' will not really sleep for 1ms, but will really sleep for ~16ms. This long-running sleep will trick Windows
         *  into using higher resolution timers.
         *
         * See: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6435126
         */
        val osType_ = osType
        if (osType_ == null || osType_.isWindows) {
            // only necessary on windows
            val timerAccuracyThread = Thread(
            {
                 while (true) {
                     try {
                         Thread.sleep(Long.MAX_VALUE)
                     } catch (ignored: Exception) {
                     }
                 }
            }, "FixWindowsHighResTimer")
            timerAccuracyThread.isDaemon = true
            timerAccuracyThread.start()
        }
    }

    /**
     * @return the System Property in a safe way for a given property, or null if it does not exist.
     */
    fun getProperty(property: String): String? {
        return getProperty(property, null)
    }

    /**
     * @return the System Property in a safe way for a given property, and if null - returns the specified default value.
     */
    fun getProperty(property: String, defaultValue: String?): String? {
        return try {
            if (System.getSecurityManager() == null) {
                System.getProperty(property, defaultValue)
            } else {
                AccessController.doPrivileged(PrivilegedAction { System.getProperty(property, defaultValue) })
            }
        } catch (ignored: Exception) {
            defaultValue
        }
    }

    /**
     * @return the value of the Java system property with the specified `property`, while falling back to the
     * specified default value if the property access fails.
     */
    fun getBoolean(property: String, defaultValue: Boolean): Boolean {
        var value = getProperty(property) ?: return defaultValue
        value = value.trim { it <= ' ' }.lowercase(Locale.getDefault())
        if (value.isEmpty()) {
            return defaultValue
        }
        if ("false" == value || "no" == value || "0" == value) {
            return false
        }
        return if ("true" == value || "yes" == value || "1" == value) {
            true
        } else defaultValue
    }

    /**
     * @return the value of the Java system property with the specified `property`, while falling back to the
     * specified default value if the property access fails.
     */
    fun getInt(property: String, defaultValue: Int): Int {
        var value = getProperty(property) ?: return defaultValue
        value = value.trim { it <= ' ' }
        try {
            return value.toInt()
        } catch (ignored: Exception) {
        }
        return defaultValue
    }

    /**
     * @return the value of the Java system property with the specified `property`, while falling back to the
     * specified default value if the property access fails.
     */
    fun getLong(property: String, defaultValue: Long): Long {
        var value = getProperty(property) ?: return defaultValue
        value = value.trim { it <= ' ' }
        try {
            return value.toLong()
        } catch (ignored: Exception) {
        }
        return defaultValue
    }

    /**
     * @return the value of the Java system property with the specified `property`, while falling back to the
     * specified default value if the property access fails.
     */
    fun getFloat(property: String, defaultValue: Float): Float {
        var value = getProperty(property) ?: return defaultValue
        value = value.trim { it <= ' ' }
        try {
            return value.toFloat()
        } catch (ignored: Exception) {
        }
        return defaultValue
    }

    /**
     * @return the value of the Java system property with the specified `property`, while falling back to the
     * specified default value if the property access fails.
     */
    fun getDouble(property: String, defaultValue: Double): Double {
        var value = getProperty(property) ?: return defaultValue
        value = value.trim { it <= ' ' }
        try {
            return value.toDouble()
        } catch (ignored: Exception) {
        }
        return defaultValue
    }

    fun getColor(property: String, defaultValue: Color): Color {
        var value = getProperty(property) ?: return defaultValue
        value = value.trim { it <= ' ' }
        try {
            return Color.decode(value)
        } catch (ignored: Exception) {
        }
        return defaultValue
    }

    /**
     * @return the OS Type that is running on this machine
     */
    fun get(): OSType? {
        return osType
    }

    fun is64bit(): Boolean {
        return osType!!.is64bit
    }

    fun is32bit(): Boolean {
        return osType!!.is32bit
    }

    /**
     * @return true if this is a "standard" x86/x64 architecture (intel/amd/etc) processor.
     */
    val isX86: Boolean
        get() = osType!!.isX86
    val isMips: Boolean
        get() = osType!!.isMips
    val isArm: Boolean
        get() = osType!!.isArm
    val isLinux: Boolean
        get() = osType!!.isLinux
    val isUnix: Boolean
        get() = osType!!.isUnix
    val isSolaris: Boolean
        get() = osType!!.isSolaris
    val isWindows: Boolean
        get() = osType!!.isWindows
    val isMacOsX: Boolean
        get() = osType!!.isMacOsX
    val isAndroid: Boolean
        get() = osType!!.isAndroid


    /**
     * Gets the currently running MAJOR java version as a NUMBER. For example, "Java version 1.7u45", and converts it into 7, uses JEP 223 for java > 9
     */
    private fun _getJavaVersion(): Int {
        // this should never be a problem, but just in case
        var fullJavaVersion = getProperty("java.version", "")
        if (fullJavaVersion!!.startsWith("1.")) {
            when (fullJavaVersion[2]) {
                '4' -> return 4
                '5' -> return 5
                '6' -> return 6
                '7' -> return 7
                '8' -> return 8
                '9' -> return 9
            }
        } else {
            // We are >= java 10, use JEP 223 to get the version (early releases of 9 might not have JEP 223, so 10 is guaranteed to have it)
            fullJavaVersion = getProperty("java.specification.version", "10")
            try {
                // it will ALWAYS be the major release version as an integer. See http://openjdk.java.net/jeps/223
                return fullJavaVersion!!.toInt()
            } catch (ignored: Exception) {
            }
        }

        // the last valid guess we have, since the current Java implementation, whatever it is, decided not to cooperate with JEP 223.
        return 10
    }

    /**
     * Set our system to UTC time zone. Retrieve the **original** time zone via [.getOriginalTimeZone]
     */
    fun setUTC() {
        // have to set our default timezone to UTC. EVERYTHING will be UTC, and if we want local, we must explicitly ask for it.
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
    }

    /**
     * @return the optimum number of threads for a given task. Makes certain not to take ALL the threads, always returns at least one
     * thread.
     */
    val optimumNumberOfThreads: Int
        get() = (Runtime.getRuntime().availableProcessors() - 2).coerceAtLeast(1)

    /**
     * @return the first line of the exception message from 'throwable', or the type if there was no message.
     */
    fun getExceptionMessage(throwable: Throwable): String? {
        var message = throwable.message
        if (message != null) {
            val index = message.indexOf(LINE_SEPARATOR)
            if (index > -1) {
                message = message.substring(0, index)
            }
        } else {
            message = throwable.javaClass.simpleName
        }
        return message
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy