• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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