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 @ConsistentCopyVisibility 24 data class ApiVersion 25 internal constructor( 26 /** The major version. */ 27 val major: Int, 28 29 /** 30 * The optional minor version. 31 * 32 * If it is `null` then neither it nor [patch] or [preReleaseQuality] are included in 33 * [toString]. If it is not `null` then it must be greater than or equal to 0. 34 */ 35 internal val minor: Int? = null, 36 37 /** 38 * The optional patch version. 39 * 40 * This must only be specified if [minor] is also specified. 41 * 42 * If it is `null` then neither it nor [preReleaseQuality] are included in [toString]. If it is 43 * not `null` then it must be greater than or equal to 0. 44 */ 45 private val patch: Int? = null, 46 47 /** 48 * The pre-release quality. 49 * 50 * This must only be specified if [patch] is also specified. 51 * 52 * If it is null then the version is assumed to have been released, and this is not included in 53 * [toString]. Otherwise, the version has not been released and this is included at the end of 54 * [toString], separated from [patch] by `-`. 55 * 56 * Any string is acceptable, but they must adhere to the rule that when the strings are sorted 57 * alphanumerically they appear in order from the lowest quality to the highest quality. 58 */ 59 private val preReleaseQuality: String? = null, 60 ) : Comparable<ApiVersion> { 61 62 // Check constraints. 63 init { <lambda>null64 require(major >= 0) { "major must be greater than or equal to 0 but was $major" } 65 66 if (minor != null) { <lambda>null67 require(minor >= 0) { "minor must be greater than or equal to 0 but was $minor" } 68 } 69 70 if (patch != null) { <lambda>null71 require(minor != null) { "patch ($patch) was specified without also specifying minor" } 72 <lambda>null73 require(patch >= 0) { "patch must be greater than or equal to 0 but was $patch" } 74 } 75 76 if (preReleaseQuality != null) { <lambda>null77 require(patch != null) { 78 "preReleaseQuality ($preReleaseQuality) was specified without also specifying patch" 79 } 80 } 81 } 82 83 /** 84 * Make sure that this is a valid version. 85 * 86 * A version of "0" is not valid as historically API levels started from 1. However, it is valid 87 * to have a [major] version of "0" as long as a [minor] version has also been provided, e.g. 88 * "0.0" is valid. 89 */ 90 val isValid 91 get() = major > 0 || minor != null 92 <lambda>null93 private val text = buildString { 94 append(major) 95 if (minor != null) { 96 append('.') 97 append(minor) 98 if (patch != null) { 99 append('.') 100 append(patch) 101 if (preReleaseQuality != null) { 102 append('-') 103 append(preReleaseQuality) 104 } 105 } 106 } 107 } 108 compareTonull109 override operator fun compareTo(other: ApiVersion) = 110 compareValuesBy( 111 this, 112 other, 113 { it.major }, <lambda>null114 { it.minor }, <lambda>null115 { it.patch }, <lambda>null116 { it.preReleaseQuality == null }, // False (released) sorts above true (pre-release) <lambda>null117 { 118 it.preReleaseQuality 119 } // Pre-release quality names are in alphabetical order from lower quality to higher 120 // quality. 121 ) 122 plusnull123 operator fun plus(increment: Int) = 124 ApiVersion(major + increment, minor, patch, preReleaseQuality) 125 126 override fun toString() = text 127 128 companion object { 129 /** Get the [ApiVersion] for [level], which must be greater than 0. */ 130 fun fromLevel(level: Int) = 131 if (level > 0) ApiVersion(level) 132 else error("level must be greater than 0 but was $level") 133 134 /** Pattern for acceptable input to [fromString]. */ 135 private val VERSION_REGEX = Pattern.compile("""^(\d+)(\.(\d+)(\.(\d+)(-(.+))?)?)?$""") 136 137 /** Index of `major` group in [VERSION_REGEX]. */ 138 private const val MAJOR_GROUP = 1 139 140 /** Index of `minor` group in [VERSION_REGEX]. */ 141 private const val MINOR_GROUP = 3 142 143 /** Index of `patch` group in [VERSION_REGEX]. */ 144 private const val PATCH_GROUP = 5 145 146 /** Index of `pre-release-quality` group in [VERSION_REGEX]. */ 147 private const val QUALITY_GROUP = 7 148 149 /** 150 * Get the [ApiVersion] for [text], which must be match 151 * `major(.minor(.patch(-quality)?)?)?`. 152 * 153 * Where `major`, `minor` and `patch` are all non-negative integers and `quality` is a 154 * string chosen such that qualities sort lexicographically from the lowest quality to the 155 * highest quality, e.g. `alpha`, `beta`, `rc` and not `good`, `bad`, `worse`. 156 */ 157 fun fromString(text: String): ApiVersion { 158 val matcher = VERSION_REGEX.matcher(text) 159 if (!matcher.matches()) { 160 error("Can not parse version: $text") 161 } 162 163 val major = matcher.group(MAJOR_GROUP).toInt() 164 val minor = matcher.group(MINOR_GROUP)?.toInt() 165 val patch = matcher.group(PATCH_GROUP)?.toInt() 166 val quality = matcher.group(QUALITY_GROUP) 167 168 return ApiVersion(major, minor, patch, quality) 169 } 170 171 /** Create an [ApiVersion] with the specified [major] and [minor] properties. */ 172 fun fromMajorMinor(major: Int, minor: Int? = null) = ApiVersion(major, minor) 173 174 /** 175 * The lowest [ApiVersion], used as the default value when higher versions override lower 176 * ones. 177 */ 178 val LOWEST = ApiVersion(0) 179 180 /** 181 * The highest [ApiVersion], used as the default value when lower versions override higher 182 * ones. 183 */ 184 val HIGHEST = ApiVersion(Int.MAX_VALUE) 185 } 186 } 187 188 /** Version of an SDK extension. */ 189 @JvmInline 190 value class ExtVersion internal constructor(val level: Int) : Comparable<ExtVersion> { 191 /** Make sure that this is a valid version. */ 192 val isValid 193 get() = level > 0 194 toStringnull195 override fun toString() = level.toString() 196 197 override operator fun compareTo(other: ExtVersion) = level.compareTo(other.level) 198 199 companion object { 200 /** Get the [ExtVersion] for [level], which must be greater than 0. */ 201 fun fromLevel(level: Int) = 202 if (level > 0) ExtVersion(level) 203 else error("level must be greater than 0 but was $level") 204 } 205 } 206