1+ package uk.q3c.util.version
2+
3+
4+ /* *
5+ * Created by David Sowerby on 24 Aug 2017
6+ */
7+
8+ data class VersionNumber @JvmOverloads constructor(
9+ val major : Int = 0 ,
10+ val minor : Int = 0 ,
11+ val patch : Int = 0 ,
12+ val nonFunctional : Int = 0 ,
13+ val qualifier : String = " " ,
14+ val buildMetaData : String = " " ,
15+ val scheme : Scheme = Scheme ()) : Comparable<VersionNumber> {
16+
17+ /* *
18+ * The [major],[minor].[patch].[nonFunctional] version elements are compared to a depth determined by [Scheme.depth].
19+ * The [qualifier] element is ignored
20+ * The [buildMetaData] element is ignored
21+ */
22+ fun isSameBaseVersionAs (other : VersionNumber ): Boolean {
23+ return compareTo(other) == 0
24+ }
25+
26+ fun isLaterBaseVersionThan (other : VersionNumber ): Boolean {
27+ return compareVersion(other, false ) > 0
28+ }
29+
30+
31+ /* *
32+ * The [major],[minor].[patch].[nonFunctional] version elements are compared to a depth determined by [Scheme.depth].
33+ * The [qualifier] elements are lexically compared (case insensitive)
34+ * The [buildMetaData] element is ignored
35+ */
36+ fun isSameVersionAs (other : VersionNumber ): Boolean {
37+ return (compareTo(other) == 0 ) && (qualifier.compareTo(other.qualifier, true ) == 0 )
38+ }
39+
40+ fun isLaterVersionThan (other : VersionNumber ): Boolean {
41+ return compareVersion(other, false ) > 0 || (compareTo(other) == 0 && (qualifier.compareTo(other.qualifier, true ) < 0 ))
42+ }
43+
44+ /* *
45+ * The [major],[minor].[patch].[nonFunctional] version elements are compared to a depth determined by [Scheme.depth].
46+ * The [qualifier] element is lexically compared (case insensitive)
47+ * The [buildMetaData] element is lexically compared (case insensitive)
48+ */
49+ fun isSameBuildAs (other : VersionNumber ): Boolean {
50+ return (compareTo(other) == 0 ) && (qualifier.compareTo(other.qualifier, true ) == 0 ) && (buildMetaData.compareTo(other.buildMetaData) == 0 )
51+ }
52+
53+ fun isLaterBuildThan (other : VersionNumber ): Boolean {
54+ return isLaterVersionThan(other) || (isSameVersionAs(other) && (buildMetaData.compareTo(other.buildMetaData) > 0 ))
55+ }
56+
57+ /* *
58+ * The [major],[minor].[patch] version elements are compared to a depth determined by [Scheme.depth].
59+ * The [nonFunctional] element is ignored
60+ * The [qualifier] element is ignored
61+ * The [buildMetaData] is ignored
62+ */
63+ fun isSameFunctionalVersionAs (other : VersionNumber ): Boolean {
64+ return (compareVersion(other, true ) == 0 ) && (qualifier.compareTo(other.qualifier, true ) == 0 )
65+ }
66+
67+ fun isLaterFunctionalVersionThan (other : VersionNumber ): Boolean {
68+ return compareVersion(other, true ) > 0 || (compareTo(other) == 0 && (qualifier.compareTo(other.qualifier, true ) < 0 ))
69+ }
70+
71+ /* *
72+ * Compares this object with the specified object for order. Returns a negative integer, zero, or a positive integer
73+ * as this object is less than, equal to, or greater than the specified object.
74+ *
75+ *
76+ * The depth to which the version is compared is set by [Scheme.depth].
77+ * The [qualifier] element is lexically compared (case insensitive) using [String.compareTo]
78+ * The [buildMetaData] element is ignored by this method
79+ */
80+ override fun compareTo (other : VersionNumber ): Int {
81+ return compareVersion(other, false )
82+ }
83+
84+ fun compareVersion (other : VersionNumber , ignoreNonfunctional : Boolean ): Int {
85+ val depth = if (ignoreNonfunctional && scheme.depth == 4 ) {
86+ 3
87+ } else {
88+ scheme.depth
89+ }
90+ if (major != other.major) {
91+ return major - other.major
92+ }
93+ if (depth == 1 ) {
94+ return 0
95+ }
96+ if (minor != other.minor) {
97+ return minor - other.minor
98+ }
99+ if (depth == 2 ) {
100+ return 0
101+ }
102+ if (patch != other.patch) {
103+ return patch - other.patch
104+ }
105+ if (depth == 3 ) {
106+ return 0
107+ }
108+ if (nonFunctional != other.nonFunctional) {
109+ return nonFunctional - other.nonFunctional
110+ }
111+ return 0
112+ }
113+
114+ /* *
115+ * Returns a new instance with [major] incremented, and ALL other elements reset to 0 (empty String for [buildMetaData] and [qualifier])
116+ */
117+ fun incMajor (): VersionNumber {
118+ return VersionNumber (major = major + 1 )
119+ }
120+
121+ /* *
122+ * Returns a new instance with [minor] incremented, and all lower order elements reset to 0 (empty String for [buildMetaData] and [qualifier])
123+ */
124+ fun incMinor (): VersionNumber {
125+ return VersionNumber (major = major, minor = minor + 1 )
126+ }
127+
128+ /* *
129+ * Returns a new instance with [patch] incremented, and all lower order elements reset to 0 (empty String for [buildMetaData] and [qualifier])
130+ */
131+ fun incPatch (): VersionNumber {
132+ return VersionNumber (major = major, minor = minor, patch = patch + 1 )
133+ }
134+
135+ /* *
136+ * Returns a new instance with [nonFunctional] incremented, [buildMetaData] and [qualifier] rest to empty String
137+ */
138+ fun incNonFunctional (): VersionNumber {
139+ return VersionNumber (major = major, minor = minor, patch = patch, nonFunctional = nonFunctional + 1 )
140+ }
141+
142+ override fun toString (): String {
143+ val buf = StringBuilder ()
144+ buf.append(major)
145+ if (scheme.depth > 1 ) {
146+ buf.append(" ." )
147+ buf.append(minor)
148+ }
149+ if (scheme.depth > 2 ) {
150+ buf.append(" ." )
151+ buf.append(patch)
152+ }
153+ if (scheme.depth > 3 ) {
154+ buf.append(" ." )
155+ buf.append(nonFunctional)
156+ }
157+ if (qualifier.isNotBlank()) {
158+ buf.append(scheme.qualifierSeparator)
159+ buf.append(qualifier)
160+ }
161+ if (buildMetaData.isNotBlank()) {
162+ buf.append(scheme.buildMetaDataSeparator)
163+ buf.append(buildMetaData)
164+ }
165+ return buf.toString()
166+ }
167+
168+
169+ }
170+
171+
172+ /* *
173+ * Parses a full version number, that is one which contains all the elements, including qualifier and build meta data, in one string
174+ * The format of that string must comply with the selected [scheme]
175+ *
176+ * @return a [VersionNumber] constructed from the input
177+ */
178+ @JvmOverloads
179+ fun parseFullVersionNumber (input : String , scheme : Scheme = Scheme ()): VersionNumber {
180+ val buildSplit = input.split(scheme.buildMetaDataSeparator)
181+ var buildMeta = " "
182+ // if there is a buildMeta element
183+ if (buildSplit.size > 1 ) {
184+ buildMeta = buildSplit[1 ]
185+ }
186+ val qualifierSplit = buildSplit[0 ].split(scheme.qualifierSeparator)
187+ var qualifier = " "
188+ if (qualifierSplit.size > 1 ) {
189+ qualifier = qualifierSplit[1 ]
190+ }
191+ val baseVersion = qualifierSplit[0 ]
192+ val bv = parseBaseVersion(baseVersion)
193+ return VersionNumber (bv.major, bv.minor, bv.patch, bv.nonFunctional, qualifier, buildMeta, scheme)
194+ }
195+
196+ /* *
197+ * Parses a version number, from its 3 constituent parst, [baseVersion] (for example 1.2.3.4), [qualifier] and [buildMetaData]
198+ *
199+ * The format of that string must comply with the selected [scheme]
200+ *
201+ * @return a [VersionNumber] constructed from the input
202+ */
203+ @JvmOverloads
204+ fun parseVersion (baseVersion : String , qualifier : String = "", buildMetaData : String = "", scheme : Scheme = Scheme ()): VersionNumber {
205+ val bv = parseBaseVersion(baseVersion)
206+ return VersionNumber (bv.major, bv.minor, bv.patch, bv.nonFunctional, qualifier, buildMetaData, scheme)
207+ }
208+
209+ /* *
210+ * Parses just the base version identifier (for example 1.2.3.4) into a simple data object containing the elements in numeric form
211+ */
212+ fun parseBaseVersion (baseVersion : String ): BaseVersion {
213+ val versionSplit = baseVersion.split(" ." )
214+ var index = 0
215+ val bv = BaseVersion ()
216+ for (element in versionSplit) {
217+ when (index) {
218+ 0 -> bv.major = element.toInt()
219+ 1 -> bv.minor = element.toInt()
220+ 2 -> bv.patch = element.toInt()
221+ 3 -> bv.nonFunctional = element.toInt()
222+
223+ }
224+ index++
225+ }
226+ return bv
227+ }
228+
229+
230+ data class Scheme @JvmOverloads constructor(val depth : Int = 4 , val qualifierSeparator : String = " -" , val buildMetaDataSeparator : String = " +" ) {
231+ init {
232+ if (depth < 1 || depth > 4 ) {
233+ throw SchemeException (" Scheme depth must be in the range 1 - 4 inclusive" )
234+ }
235+ }
236+ }
237+
238+ data class BaseVersion (var major : Int = 0 ,
239+ var minor : Int = 0 ,
240+ var patch : Int = 0 ,
241+ var nonFunctional : Int = 0 )
242+
243+ class SchemeException (msg : String ) : RuntimeException(msg)
0 commit comments