1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tools.metalava.apilevels 18 19 import com.android.tools.metalava.apilevels.ApiVersion.Companion.toString 20 import java.util.regex.Pattern 21 22 /** Version of an SDK, e.g. Android or AndroidX. */ 23 data class ApiVersion 24 internal constructor( 25 /** The major version. */ 26 private val major: Int, 27 28 /** 29 * The optional minor version. 30 * 31 * If it is `null` then neither it nor [patch] or [preReleaseQuality] are included in 32 * [toString]. If it is not `null` then it must be greater than or equal to 0. 33 */ 34 private val minor: Int? = null, 35 36 /** 37 * The optional patch version. 38 * 39 * This must only be specified if [minor] is also specified. 40 * 41 * If it is `null` then neither it nor [preReleaseQuality] are included in [toString]. If it is 42 * not `null` then it must be greater than or equal to 0. 43 */ 44 private val patch: Int? = null, 45 46 /** 47 * The pre-release quality. 48 * 49 * This must only be specified if [patch] is also specified. 50 * 51 * If it is null then the version is assumed to have been released, and this is not included in 52 * [toString]. Otherwise, the version has not been released and this is included at the end of 53 * [toString], separated from [patch] by `-`. 54 * 55 * Any string is acceptable, but they must adhere to the rule that when the strings are sorted 56 * alphanumerically they appear in order from the lowest quality to the highest quality. 57 */ 58 private val preReleaseQuality: String? = null, 59 ) : Comparable<ApiVersion> { 60 61 // Check constraints. 62 init { <lambda>null63 require(major >= 0) { "major must be greater than or equal to 0 but was $major" } 64 65 if (minor != null) { <lambda>null66 require(minor >= 0) { "minor must be greater than or equal to 0 but was $minor" } 67 } 68 69 if (patch != null) { <lambda>null70 require(minor != null) { "patch ($patch) was specified without also specifying minor" } 71 <lambda>null72 require(patch >= 0) { "patch must be greater than or equal to 0 but was $patch" } 73 } 74 75 if (preReleaseQuality != null) { <lambda>null76 require(patch != null) { 77 "preReleaseQuality ($preReleaseQuality) was specified without also specifying patch" 78 } 79 } 80 } 81 82 /** 83 * Make sure that this is a valid version. 84 * 85 * A version of "0" is not valid as historically API levels started from 1. However, it is valid 86 * to have a [major] version of "0" as long as a [minor] version has also been provided, e.g. 87 * "0.0" is valid. 88 */ 89 val isValid 90 get() = major > 0 || minor != null 91 <lambda>null92 private val text = buildString { 93 append(major) 94 if (minor != null) { 95 append('.') 96 append(minor) 97 if (patch != null) { 98 append('.') 99 append(patch) 100 if (preReleaseQuality != null) { 101 append('-') 102 append(preReleaseQuality) 103 } 104 } 105 } 106 } 107 compareTonull108 override operator fun compareTo(other: ApiVersion) = 109 compareValuesBy( 110 this, 111 other, 112 { it.major }, <lambda>null113 { it.minor }, <lambda>null114 { it.patch }, <lambda>null115 { it.preReleaseQuality == null }, // False (released) sorts above true (pre-release) <lambda>null116 { 117 it.preReleaseQuality 118 } // Pre-release quality names are in alphabetical order from lower quality to higher 119 // quality. 120 ) 121 plusnull122 operator fun plus(increment: Int) = 123 ApiVersion(major + increment, minor, patch, preReleaseQuality) 124 125 override fun toString() = text 126 127 companion object { 128 /** Get the [ApiVersion] for [level], which must be greater than 0. */ 129 fun fromLevel(level: Int) = 130 if (level > 0) ApiVersion(level) 131 else error("level must be greater than 0 but was $level") 132 133 /** Pattern for acceptable input to [fromString]. */ 134 private val VERSION_REGEX = Pattern.compile("""^(\d+)(\.(\d+)(\.(\d+)(-(.+))?)?)?$""") 135 136 /** Index of `major` group in [VERSION_REGEX]. */ 137 private const val MAJOR_GROUP = 1 138 139 /** Index of `minor` group in [VERSION_REGEX]. */ 140 private const val MINOR_GROUP = 3 141 142 /** Index of `patch` group in [VERSION_REGEX]. */ 143 private const val PATCH_GROUP = 5 144 145 /** Index of `pre-release-quality` group in [VERSION_REGEX]. */ 146 private const val QUALITY_GROUP = 7 147 148 /** 149 * Get the [ApiVersion] for [text], which must be match 150 * `major(.minor(.patch(-quality)?)?)?`. 151 * 152 * Where `major`, `minor` and `patch` are all non-negative integers and `quality` is a 153 * string chosen such that qualities sort lexicographically from the lowest quality to the 154 * highest quality, e.g. `alpha`, `beta`, `rc` and not `good`, `bad`, `worse`. 155 */ 156 fun fromString(text: String): ApiVersion { 157 val matcher = VERSION_REGEX.matcher(text) 158 if (!matcher.matches()) { 159 error("Can not parse version: $text") 160 } 161 162 val major = matcher.group(MAJOR_GROUP).toInt() 163 val minor = matcher.group(MINOR_GROUP)?.toInt() 164 val patch = matcher.group(PATCH_GROUP)?.toInt() 165 val quality = matcher.group(QUALITY_GROUP) 166 167 return ApiVersion(major, minor, patch, quality) 168 } 169 170 /** 171 * The lowest [ApiVersion], used as the default value when higher versions override lower 172 * ones. 173 */ 174 val LOWEST = ApiVersion(0) 175 176 /** 177 * The highest [ApiVersion], used as the default value when lower versions override higher 178 * ones. 179 */ 180 val HIGHEST = ApiVersion(Int.MAX_VALUE) 181 } 182 } 183 184 /** Version of an SDK extension. */ 185 @JvmInline 186 value class ExtVersion internal constructor(val level: Int) : Comparable<ExtVersion> { 187 /** Make sure that this is a valid version. */ 188 val isValid 189 get() = level > 0 190 toStringnull191 override fun toString() = level.toString() 192 193 override operator fun compareTo(other: ExtVersion) = level.compareTo(other.level) 194 195 companion object { 196 /** Get the [ExtVersion] for [level], which must be greater than 0. */ 197 fun fromLevel(level: Int) = 198 if (level > 0) ExtVersion(level) 199 else error("level must be greater than 0 but was $level") 200 } 201 } 202