Improve default support for version schemes
The current implementation for version schemes is rather limited. It only supports 5 properties:
- Major versoin
- Minor version
- Incremental version (bugfix)
- Build number
- A Qualifier.
Flaws:
- It only supports the following schemes:
- 'positiveInteger-buildnumber' where buildnumber doesn't start with '0' and has to be
- 'positiveinteger-qualifier'
- positiveInteger(.positiveInteger(.positiveInteger))-(buildNr|qualifier)
- Inconsistent/unintuitive parsing:
- in something-X, where X is [1..9], X will be a buildnumber
- in something-0X, where X is any string, '0X' will be a qualifier
- something.0something will yield 0.0.0.0-something.0something
- something.NaN will also yield 0.0.0.0-something.NaN
- getBuildNumber returns '0' when no buildnumber is specified, yet you can never specify 0 as a buildnumber
- qualifiers are sorted lexically
- if a qualifier is a prefix of another, the longest one wins (example:'1.0-alpha10' is considered older than '1.0-alpha2')
Proposal
I'm proposing the following implementation: GenericArtifactVersion.java (unit test: GenericArtifactVersionTest.java)
Features:
- Mixing of '-' and '.' separators
- Transition between characters and digits also constitutes a separator:
- 1.0alpha1 => [1, 0, alpha, 1]; This fixes '1.0alpha10 < 1.0alpha2'
- Unlimited number of version components
- Version components in the text can be digits or strings
- strings are checked for well-known qualifiers and the qualifier ordering is used for version ordering
- well-known qualifiers (case insensitive)
- snapshot (NOTE; snapshot needs discussion)
- alpha
- beta
- rc
- (the empty string)
- ga
- sp
- well-known qualifiers (case insensitive)
- version components prefixed with '-' will result in a sub-list of version components.
A dash usually precedes a qualifier, and is always less important than something preceded with a dot.
We need to somehow record the separators themselves, which is done by sublists.
Parse examples:- 1.0-alpha1 => [1, 0, ["alpha", 1]]
- 1.0-rc-2 => [1, 0, ["rc", [2]]]
Parsing versions
The version string is examined one character at a time.
There's a buffer containing the current text - all characters are appended, except for '.' and '-'.
Below, when it's stated 'append buffer to list', the buffer is first converted to an Integer item if that's possible, otherwise left alone as a String. It will only be appended if it's length is not 0.
- If a '.' is encountered, the current buffer is appended to the current list, either as a IntegerItem (if it's a number) or a StringItem.
- If a '-' is encountered, do the same as when a '.' is encountered, then create a new sublist, append it to the current list and replace the current list with the new sub-list.
- If the last character was a digit:
- and the current one is too, append it to the buffer.
- otherwise append the current buffer to the list, reset the buffer with the current char as content
- if the last character was NOT a digit:
- if the last character was also NOT a digit, append it to the buffer
- if it is a digit, append buffer to list, set buffers content to the digit
- finally, append the buffer to the list
Some examples:
- 1.0 => [1, 0]
- 1.0.1 => [1, 0, 1]
- 1-SNAPSHOT => [1, ["SNAPSHOT"]]
- 1-alpha10-SNAPSHOT => [1, ["alpha", "10", ["SNAPSHOT"]]]
Ordering algorithm
Internally 3 version component types are used:
- integer (IntegerItem)
- string (StringItem) (knows if it's a qualifier or not)
- sublist (ListItem)
Elements from both versions are compared one at a time; first the first element of both, then the second, etc.
(Note: 'item' and 'component' are used interchangeably)
ordering rules when comparing version components:
|
Integer |
String |
List |
null |
|---|---|---|---|---|
Integer |
Highest is newer |
Integer is newer |
Integer is newer |
If integer==0 then equal, |
String |
Integer is newer |
order by well-known |
List is newer |
Compare with "" |
List |
Integer is newer |
List is newer |
Version itself is a list; compare item by item |
Compare with empty list item (recursion) |
null |
If integer==0 then equal, |
Compare with "" |
Compare with empty list item (recursion) |
doesn't happen |
Special note on string comparing:
A predefined list of well-known qualifiers is present. For comparison, the string is converted to another string, as follows:
- First, the well-known qualifier list is consulted for presence of the string
- If the string is present, the index in the list is returned, as a string
- If the string is not present, then qualifiers.size() + "-" + string is returned.
Then the strings are lexically compared.
Examples:
- "alpha" yields "1"
- "" yields "4"
- "abc" yields "7-abc"
- "xyz" yields "7-xyz"
String Compare examples:
- 1.0 ==? 1.0-alpha: "" (or null) ==? "alpha" -> "4" ==? "1" -> 1.0 is newer
- 1 ==? 1.0: equal
- 1-beta ==? 1-xyz: "2" ==? "7-xyz" -> 1-xyz is newer
Some comparisons that yield different results from the current implementation:
- 1-beta ==? 1-abc: "2" ==? "7-abc" -> 1-abc is newer
- 1.0 ==? 1.0-abc: "4" ==? "7-abc" -> 1.0-abc is newer
- 1.0-alpha-10 ==? 1.0-alpha-2: 10 > 2, so '1.0-alpha-10' is newer
- 1.0-alpha-1.0 ==? 1.0-alpha-1: equal
- 1.0-alpha-1.2 ==? 1.0-alpha-2: 1.0-alpha-2 is newer
Make version handling pluggable
tbd
Define a grammar for version specifications
tbd