com.datastax.oss.driver.api.core.Version Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.datastax.oss.driver.api.core;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.jcip.annotations.Immutable;
/**
* A structured version number.
*
* It is in the form X.Y.Z, with optional pre-release labels and build metadata.
*
*
Version numbers compare the usual way, the major number (X) is compared first, then the minor
* one (Y) and then the patch level one (Z). Lastly, versions with pre-release sorts before the
* versions that don't have one, and labels are sorted alphabetically if necessary. Build metadata
* are ignored for sorting versions.
*/
@Immutable
public class Version implements Comparable, Serializable {
private static final long serialVersionUID = 1;
private static final String VERSION_REGEXP =
"(\\d+)\\.(\\d+)(\\.\\d+)?(\\.\\d+)?([~\\-]\\w[.\\w]*(?:-\\w[.\\w]*)*)?(\\+[.\\w]+)?";
private static final Pattern pattern = Pattern.compile(VERSION_REGEXP);
@NonNull public static final Version V2_1_0 = Objects.requireNonNull(parse("2.1.0"));
@NonNull public static final Version V2_2_0 = Objects.requireNonNull(parse("2.2.0"));
@NonNull public static final Version V3_0_0 = Objects.requireNonNull(parse("3.0.0"));
@NonNull public static final Version V4_0_0 = Objects.requireNonNull(parse("4.0.0"));
@NonNull public static final Version V4_1_0 = Objects.requireNonNull(parse("4.1.0"));
@NonNull public static final Version V5_0_0 = Objects.requireNonNull(parse("5.0.0"));
@NonNull public static final Version V6_7_0 = Objects.requireNonNull(parse("6.7.0"));
@NonNull public static final Version V6_8_0 = Objects.requireNonNull(parse("6.8.0"));
private final int major;
private final int minor;
private final int patch;
private final int dsePatch;
private final String[] preReleases;
private final String build;
private Version(
int major, int minor, int patch, int dsePatch, String[] preReleases, String build) {
this.major = major;
this.minor = minor;
this.patch = patch;
this.dsePatch = dsePatch;
this.preReleases = preReleases;
this.build = build;
}
/**
* Parses a version from a string.
*
* The version string should have primarily the form X.Y.Z to which can be appended one or more
* pre-release label after dashes (2.0.1-beta1, 2.1.4-rc1-SNAPSHOT) and an optional build label
* (2.1.0-beta1+a20ba.sha). Out of convenience, the "patch" version number, Z, can be omitted, in
* which case it is assumed to be 0.
*
* @param version the string to parse.
* @return the parsed version number.
* @throws IllegalArgumentException if the provided string does not represent a valid version.
*/
@Nullable
public static Version parse(@Nullable String version) {
if (version == null) {
return null;
}
Matcher matcher = pattern.matcher(version);
if (!matcher.matches()) {
throw new IllegalArgumentException("Invalid version number: " + version);
}
try {
int major = Integer.parseInt(matcher.group(1));
int minor = Integer.parseInt(matcher.group(2));
String pa = matcher.group(3);
int patch =
pa == null || pa.isEmpty()
? 0
: Integer.parseInt(
pa.substring(1)); // dropping the initial '.' since it's included this time
String dse = matcher.group(4);
int dsePatch =
dse == null || dse.isEmpty()
? -1
: Integer.parseInt(
dse.substring(1)); // dropping the initial '.' since it's included this time
String pr = matcher.group(5);
String[] preReleases =
pr == null || pr.isEmpty()
? null
: pr.substring(1)
.split("-"); // drop initial '-' or '~' then split on the remaining ones
String bl = matcher.group(6);
String build = bl == null || bl.isEmpty() ? null : bl.substring(1); // drop the initial '+'
return new Version(major, minor, patch, dsePatch, preReleases, build);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid version number: " + version);
}
}
/**
* The major version number.
*
* @return the major version number, i.e. X in X.Y.Z.
*/
public int getMajor() {
return major;
}
/**
* The minor version number.
*
* @return the minor version number, i.e. Y in X.Y.Z.
*/
public int getMinor() {
return minor;
}
/**
* The patch version number.
*
* @return the patch version number, i.e. Z in X.Y.Z.
*/
public int getPatch() {
return patch;
}
/**
* The DSE patch version number (will only be present for version of Cassandra in DSE).
*
*
DataStax Entreprise (DSE) adds a fourth number to the version number to track potential hot
* fixes and/or DSE specific patches that may have been applied to the Cassandra version. In that
* case, this method returns that fourth number.
*
* @return the DSE patch version number, i.e. D in X.Y.Z.D, or -1 if the version number is not
* from DSE.
*/
public int getDSEPatch() {
return dsePatch;
}
/**
* The pre-release labels if relevant, i.e. label1 and label2 in X.Y.Z-label1-lable2.
*
* @return the pre-release labels. The return list will be {@code null} if the version number
* doesn't have any.
*/
public List getPreReleaseLabels() {
return preReleases == null ? null : Collections.unmodifiableList(Arrays.asList(preReleases));
}
/**
* The build label if there is one.
*
* @return the build label or {@code null} if the version number doesn't have one.
*/
public String getBuildLabel() {
return build;
}
/**
* The next stable version, i.e. the version stripped of its pre-release labels and build
* metadata.
*
* This is mostly used during our development stage, where we test the driver against
* pre-release versions of Cassandra like 2.1.0-rc7-SNAPSHOT, but need to compare to the stable
* version 2.1.0 when testing for native protocol compatibility, etc.
*
* @return the next stable version.
*/
public Version nextStable() {
return new Version(major, minor, patch, dsePatch, null, null);
}
@Override
public int compareTo(@NonNull Version other) {
if (major < other.major) {
return -1;
}
if (major > other.major) {
return 1;
}
if (minor < other.minor) {
return -1;
}
if (minor > other.minor) {
return 1;
}
if (patch < other.patch) {
return -1;
}
if (patch > other.patch) {
return 1;
}
if (dsePatch < 0) {
if (other.dsePatch >= 0) {
return -1;
}
} else {
if (other.dsePatch < 0) {
return 1;
}
// Both are >= 0
if (dsePatch < other.dsePatch) {
return -1;
}
if (dsePatch > other.dsePatch) {
return 1;
}
}
if (preReleases == null) {
return other.preReleases == null ? 0 : 1;
}
if (other.preReleases == null) {
return -1;
}
for (int i = 0; i < Math.min(preReleases.length, other.preReleases.length); i++) {
int cmp = preReleases[i].compareTo(other.preReleases[i]);
if (cmp != 0) {
return cmp;
}
}
return Integer.compare(preReleases.length, other.preReleases.length);
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
} else if (other instanceof Version) {
Version that = (Version) other;
return this.major == that.major
&& this.minor == that.minor
&& this.patch == that.patch
&& this.dsePatch == that.dsePatch
&& (this.preReleases == null
? that.preReleases == null
: Arrays.equals(this.preReleases, that.preReleases))
&& Objects.equals(this.build, that.build);
} else {
return false;
}
}
@Override
public int hashCode() {
return Objects.hash(major, minor, patch, dsePatch, Arrays.hashCode(preReleases), build);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(major).append('.').append(minor).append('.').append(patch);
if (dsePatch >= 0) {
sb.append('.').append(dsePatch);
}
if (preReleases != null) {
for (String preRelease : preReleases) {
sb.append('-').append(preRelease);
}
}
if (build != null) {
sb.append('+').append(build);
}
return sb.toString();
}
}