issue tracked as MNG-3010

Improve default support for version schemes

The current implementation for version schemes is rather limited. It only supports 5 properties:

  1. Major versoin
  2. Minor version
  3. Incremental version (bugfix)
  4. Build number
  5. A Qualifier.

Flaws

Other than the limitation of supported versions, the current implementation has several flaws:

The unit tests that are there to test comparison only check for a few cases with snapshots. When all tests
(version A < version B) are expanded to also test for SNAPSHOTs (version A-SNAPSHOT < version B-SNAPSHOT) a
lot of the tests fail.

Left

Op

Right

 

Left

Op

Right

 

 

1

=

1

(tick)

1-SNAPSHOT

=

1-SNAPSHOT

(tick)

 

1

<

2

(tick)

1-SNAPSHOT

<

2-SNAPSHOT

(tick)

 

1.5

<

2

(tick)

1.5-SNAPSHOT

<

2-SNAPSHOT

(tick)

 

1

<

2.5

(tick)

1-SNAPSHOT

<

2.5-SNAPSHOT

(tick)

 

1

=

1.0

(tick)

1-SNAPSHOT

=

1.0-SNAPSHOT

(tick)

 

1

=

1.0.0

(tick)

1-SNAPSHOT

=

1.0.0-SNAPSHOT

(tick)

 

1.0

<

1.1

(tick)

1.0-SNAPSHOT

<

1.1-SNAPSHOT

(tick)

 

1.1

<

1.2

(tick)

1.1-SNAPSHOT

<

1.2-SNAPSHOT

(tick)

 

1.0.0

<

1.1

(tick)

1.0.0-SNAPSHOT

<

1.1-SNAPSHOT

(tick)

 

1.1

<

1.2.0

(tick)

1.1-SNAPSHOT

<

1.2.0-SNAPSHOT

(tick)

 

1.0-alpha-1

<

1.0

(tick)

1.0-alpha-1-SNAPSHOT

<

1.0-SNAPSHOT

(error)

 

1.0-alpha-1

<

1.0-alpha-2

(tick)

1.0-alpha-1-SNAPSHOT

<

1.0-alpha-2-SNAPSHOT

(tick)

 

1.0-alpha-1

<

1.0-beta-1

(tick)

1.0-alpha-1-SNAPSHOT

<

1.0-beta-1-SNAPSHOT

(tick)

 

1.0

<

1.0-1

(tick)

1.0-SNAPSHOT

<

1.0-1-SNAPSHOT

(error)

 

1.0-1

<

1.0-2

(tick)

1.0-1-SNAPSHOT

<

1.0-2-SNAPSHOT

(error)

 

2.0-0

=

2.0

(tick)

2.0-0-SNAPSHOT

=

2.0-SNAPSHOT

(error)

 

2.0

<

2.0-1

(tick)

2.0-SNAPSHOT

<

2.0-1-SNAPSHOT

(error)

 

2.0.0

<

2.0-1

(tick)

2.0.0-SNAPSHOT

<

2.0-1-SNAPSHOT

(error)

 

2.0-1

<

2.0.1

(tick)

2.0-1-SNAPSHOT

<

2.0.1-SNAPSHOT

(tick)

 

2.0.1-klm

<

2.0.1-lmn

(tick)

2.0.1-klm-SNAPSHOT

<

2.0.1-lmn-SNAPSHOT

(tick)

 

2.0.1-xyz

<

2.0.1

(tick)

2.0.1-xyz-SNAPSHOT

<

2.0.1-SNAPSHOT

(error)

notice that the proposal reverted the order: in Maven 2, 2.0.1-xyz < 2.0.1 but in Maven 3, 2.0.1 < 2.0.1-xyz

2.0.1

<

2.0.1-123

(tick)

2.0.1-SNAPSHOT

<

2.0.1-123-SNAPSHOT

(error)

 

2.0.1-xyz

<

2.0.1-123

(tick)

2.0.1-xyz-SNAPSHOT

<

2.0.1-123-SNAPSHOT

(error)

 

Proposal

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:

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.

Some examples:

Ordering algorithm

Internally 3 version component types are used:

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,
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

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:

Then the strings are lexically compared.

Examples:

String Compare examples:

Some comparisons that yield different results from the current implementation:

Note: This approach differs from the version comparison as done by OSGi [0].

Note: this cause a change from Maven 2 for unknown qualifiers: in Maven 2, 2.0.1-xyz < 2.0.1 but in Maven 3, 2.0.1 < 2.0.1-xyz

Make version handling pluggable (not implemented)

Pluggable version handling is not implemented in Maven core dependency resolution since considered hardly manageable,
but it is implemented in versions-maven-plugin for its versions management tasks.
See the version numbers rules documentation.

When somebody devices a version scheme that cannot be handled by the above, it should be possible to plug in a new scheme. Two possible scenarios for unsupported schemes:

To make version schemes pluggable, the following is required:

Define a grammar for version specifications

I'm not entirely sure this is necessary, but for other languages that cannot use pre-packaged version scheme implementations in Java, we need to have some sort of metadata, preferrably in the version-scheme artifact, describing the version scheme. Perhaps something like version-scheme-1.0.jar!/META-INF/maven/version-scheme.file-extension 

Several proposals have been made for the version scheme description language:

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

<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>


A sample scheme:

<versionSchemeDefinition>
  <qualifierDefinition name="snapshot"/>
  <qualifierDefinition name="alpha"/>
  <qualifierDefinition name="beta"/>
  <qualifierDefinition name="rc"/>
  <qualifierDefinition name=""/>
  <qualifierDefinition name="ga"/>
  <qualifierDefinition name="sp"/>
  <numberComponent name="major"/>
  <numberComponent name="minor" optional="true"/>
  <numberComponent name="micro" optional="true"/>
  <stringComponent name="qualifier" optional="true"/>
  <numberComponent name="buildnumber" optional="true"/>
</versionSchemeDefinition>


This scheme doesn't even come close to being able to describe the variety of version schemes supported by my proposal.
Perhaps it's better if, when we do XML, we use XSD to describe the version schemes.
We define a set of simple/complextypes that people have to extend, and the engine can then convert it to a parser/verifier/order implementation/representation using the base classes.
The parser would use the 'prefix' values and the type attributes to determine what kind of token is next, in the version string. It would then build an XML DOM that can be validated against the XSD, which can have extra rules.
Anyway, UDDI tried to do something similar for random applications - to have the API specs in a uniform format so you could generate an application around reusable components, but that didn't work out and AFAIK the specs are just human readable documents. If somebody wants to create a parser/validator/sorter for a version spec, there should just be enough documentation for them to do it.



References and Related Material

[0] OSGi Service Platform Release 4 Version 4.1 Core Specification, §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