EngineVersion.kt

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.concept.engine.utils

/**
 * Release type - as compiled - of the engine.
 */
enum class EngineReleaseChannel {
    UNKNOWN,
    NIGHTLY,
    BETA,
    RELEASE,
    DEFAULT, // The update channel is "default" for non-shippable builds.
}

/**
 * Data class for engine versions using semantic versioning (major.minor.patch).
 *
 * @param major Major version number
 * @param minor Minor version number
 * @param patch Patch version number
 * @param metadata Additional and optional metadata appended to the version number, e.g. for a version number of
 * "68.0a1" [metadata] will contain "a1".
 * @param releaseChannel Additional property indicating the release channel of this version.
 */
data class EngineVersion(
    val major: Int,
    val minor: Int,
    val patch: Long,
    val metadata: String? = null,
    val releaseChannel: EngineReleaseChannel = EngineReleaseChannel.UNKNOWN,
) {
    operator fun compareTo(other: EngineVersion): Int {
        return when {
            major != other.major -> major - other.major
            minor != other.minor -> minor - other.minor
            patch != other.patch -> (patch - other.patch).toInt()
            metadata != other.metadata -> when {
                metadata == null -> -1
                other.metadata == null -> 1
                else -> metadata.compareTo(other.metadata)
            }
            releaseChannel != other.releaseChannel -> releaseChannel.compareTo(other.releaseChannel)
            else -> 0
        }
    }

    /**
     * Returns true if this version number equals or is higher than the provided [major], [minor], [patch] version
     * numbers.
     */
    fun isAtLeast(major: Int, minor: Int = 0, patch: Long = 0): Boolean {
        return when {
            this.major > major -> true
            this.major < major -> false
            this.minor > minor -> true
            this.minor < minor -> false
            this.patch >= patch -> true
            else -> false
        }
    }

    override fun toString(): String {
        return buildString {
            append(major)
            append(".")
            append(minor)
            append(".")
            append(patch)
            if (metadata != null) {
                append(metadata)
            }
        }
    }

    companion object {
        /**
         * Parses the given [version] string and returns an [EngineVersion]. Returns null if the [version] string could
         * not be parsed successfully.
         */
        @Suppress("MagicNumber", "ReturnCount")
        fun parse(version: String, releaseChannel: String? = null): EngineVersion? {
            val majorRegex = "([0-9]+)"
            val minorRegex = "\\.([0-9]+)"
            val patchRegex = "(?:\\.([0-9]+))?"
            val metadataRegex = "([^0-9].*)?"
            val regex = "$majorRegex$minorRegex$patchRegex$metadataRegex".toRegex()
            val result = regex.matchEntire(version) ?: return null

            val major = result.groups[1]?.value ?: return null
            val minor = result.groups[2]?.value ?: return null
            val patch = result.groups[3]?.value ?: "0"
            val metadata = result.groups[4]?.value
            val engineReleaseChannel = when (releaseChannel) {
                "nightly" -> EngineReleaseChannel.NIGHTLY
                "beta" -> EngineReleaseChannel.BETA
                "release" -> EngineReleaseChannel.RELEASE
                "default" -> EngineReleaseChannel.DEFAULT
                else -> EngineReleaseChannel.UNKNOWN
            }

            return try {
                EngineVersion(
                    major.toInt(),
                    minor.toInt(),
                    patch.toLong(),
                    metadata,
                    engineReleaseChannel,
                )
            } catch (e: NumberFormatException) {
                null
            }
        }
    }
}