Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: added link to MNG-3010

issue tracked as MNG-3010

Improve default support for version schemes

...

  • 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:
    • Wiki Markupin 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 shorter one is considered newer (example:'1.0-alpha10' is considered older than '1.0-alpha1')

...

I'm proposing the following implementation: GenericArtifactVersion.java (unit test: GenericArtifactVersionTest.java). It has been integrated in artifact 3.0-SNAPSHOT r656775(15/5/2008) as ComparableVersion.java.

Features:

  • Mixing of '-' (dash) and '.' (dot) separators
  • Transition between characters and digits also constitutes a separator:
      unmigrated-wiki-markup
    • 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)
      • alpha or a
      • beta or b
      • milestone or m
      • rc or cr
      • snapshot (NOTE; snapshot needs discussion)
      • (the empty string) or ga or final
      • sp
  • 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:
      unmigrated-wiki-markup
    • 1.0-alpha1 => \ [1, 0, \ ["alpha", 1\]\]unmigrated-wiki-markup
    • 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\]unmigrated-wiki-markup
  • 1.0.1 => \ [1, 0, 1\]
  • Wiki Markup1-SNAPSHOT => \ [1, \ ["SNAPSHOT"\]\] Wiki Markup
  • 1-alpha10-SNAPSHOT => \ [1, \ ["alpha", "10", \ ["SNAPSHOT"\]\]\]

Ordering algorithm

Internally 3 version component types are used:

...

 

Integer

String

List

null

Integer

Highest is newer

Integer is newer

Integer is newer

If integer==0 then equal,
otherwise integer is newer

String

Integer is newer

order by well-known
qualifiers and lexically
(see below)

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)
this will finally result in String==?null or
Integer==?null

null

If integer==0 then equal,
otherwise integer is newer

Compare with "" 

Compare with empty list item (recursion)
this will finally result in String==?null or
Integer==?null

doesn't happen

...

To make version schemes pluggable, the following is required:

  • A POM change to support something like this to identify a version-scheme implementation artifact:

    No Format
    <versionScheme>
       <groupId>..</groupId>
       <artifactId>..</artifactId>
       <version>..</version> <\!-\-  we may need to disallow version ranges here \-->
    </versionScheme>
    
  • Maven-metadata at the artifact level needs to include the tag above. We'll limit version schemes on a per artifact basis. This is required in order to resolve versions using ranges.
  • An interface definition for VersionScheme
  • A way to detect what is the version class inside the version-scheme artifact; I hope we can use plexus, as long as multiple version-scheme implementations (same hint, same package/classname) can be accessed simultaneously without conflict.
  • Refactoring the version code out of maven-artifact so plugin code etc. can use it too
  • The super pom will contain a default versionScheme tag listing the maven internal implementation

...

As an example here's an XSD you could use to describe versions:

No Format
<xs:schema>
  <xs:element name="versionSchemeDefinition">
    <xs:complexType>
      <xs:sequence>
        <!-- the order of qualifierDefinitions is from oldest to newest -->
        <xs:element ref="qualifierDefinition" minOccurs="0" maxOccurs="unbounded"/>
        <xs:choice maxOccurs="unbounded" minOccurs="0">
          <xs:element ref="stringComponent"/>
          <xs:element ref="numberComponent"/>
          <xs:element ref="subComponent"/>
        </xs:choice>
      </xs:sequence>
    </xs:complexType>
  </xs:element>

  <xs:element name="qualifierDefinition">
    <xs:complexType>
      <xs:attribute name="name" type="xs:string"/>
      <xs:attribute name="caseSensitive" type="xs:boolean" default="false"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="stringComponent">
    <xs:complexType>
      <xs:attribute name="name" type="xs:string"/>
      <xs:attribute name="prefix" type="xs:string" default="."/>
    </xs:complexType>
  </xs:element>

  <xs:element name="numberComponent" type="xs:int">
    <xs:complexType>
      <xs:attribute name="name" type="xs:string"/>
      <xs:attribute name="prefix" type="xs:string" default="."/>
      <xs:attribute name="optional" type="xs:boolean" default="false"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="subComponent">
    <xs:complexType>
      <xs:choice maxOccurs="unbounded" minOccurs="0">
        <xs:element ref="stringComponent"/>
        <xs:element ref="numberComponent"/>
        <xs:element ref="subComponent"/>
      </xs:choice>
      <xs:attribute name="name" type="xs:string"/>
      <xs:attribute name="prefix" type="xs:string" default="-"/>
    </xs:complexType>
  </xs:element>
</xs:schema>

...

References and Related Material

Anchor
osgi
osgi
[0] OSGi Service Platform Release 4 Version 4.1 Core Specification, Wiki Markup<ac:structured-macro ac:name="anchor" ac:schema-version="1" ac:macro-id="027ebd7d-cab0-43b8-85db-f13694afd199"><ac:parameter ac:name="">osgi</ac:parameter></ac:structured-macro> \[0\] [OSGi Service Platform Release 4 Version 4.1 Core Specification|http://www.osgi.org/Release4/Download], §3.2.4 "Version" and §3.2.5 "Version Ranges" on page 38, §3.5.3 "Bundle-Version" on page 46, §6.1.26.5 "Version.compareTo()" on page 200