Added version utility class to manage comparing versions. X.X

.X-BETA-hash is the max supported
This commit is contained in:
nathan 2017-07-17 00:34:36 +02:00
parent 4f20a7524b
commit bb15ca12a8

View File

@ -0,0 +1,411 @@
/*
* Copyright 2017 dorkbox, llc
*
* Licensed 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 dorkbox.util;
import java.util.Locale;
/**
* Version a = new Version("1.1");
* Version b = new Version("1.1.1");
* a.compareTo(b) // return -1 (a<b)
* a.equals(b) // return false
* <p>
* Version a = new Version("2.0");
* Version b = new Version("1.9.9");
* a.compareTo(b) // return 1 (a>b)
* a.equals(b) // return false
* <p>
* Version a = new Version("1.0");
* Version b = new Version("1");
* a.compareTo(b) // return 0 (a=b)
* a.equals(b) // return true
* <p>
* Version a = new Version("1");
* Version b = null;
* a.compareTo(b) // return 1 (a>b)
* a.equals(b) // return false
* <p>
* List<Version> versions = new ArrayList<Version>();
* versions.add(new Version("2"));
* versions.add(new Version("1.0.5"));
* versions.add(new Version("1.01.0"));
* versions.add(new Version("1.00.1"));
* Collections.min(versions).get() // return min version
* Collections.max(versions).get() // return max version
* <p>
* // WARNING
* Version a = new Version("2.06");
* Version b = new Version("2.060");
* a.equals(b) // return false
* <p>
* <p>
* If the numbers are the same, then
* BETA+BUILD < BETA < STABLE+BUILD < STABLE.
* <p>
* Stable is a version that is exclusively numbers. Builds are always equal, even if a different build commit hash/etc.
*/
@SuppressWarnings({"unused", "SimplifiableIfStatement"})
public
class Version implements Comparable<Version> {
private static final int[] PRIME = {2, 3, 5};
private final String version;
private final int[] internalVersion;
private final boolean isBeta;
private final String build;
/**
* Creates a comparable version based on only numbers
*
* @param version must consist of just numbers with a maximum of 3 groups separated by a .
*/
public
Version(String version) {
this(version, false, null);
}
/**
* Creates a comparable version based on numbers, BETA status, and BUILD
*
* @param version must consist of only numbers with a maximum of 3 groups separated by a .
* @param isBeta true if this is a beta build
* @param build custom build info, such as the commit sha hash
*/
public
Version(String version, boolean isBeta, String build) {
if (version == null) {
throw new IllegalArgumentException("Version can not be null");
}
if (!version.matches("[0-9]+(\\.[0-9]+){0,3}")) {
throw new IllegalArgumentException("Invalid version format");
}
if (build != null) {
this.build = build.toLowerCase(Locale.US);
}
else {
this.build = null;
}
this.isBeta = isBeta;
this.version = version;
String[] parts = this.version.split("\\.");
internalVersion = new int[parts.length];
for (int i = 0; i < parts.length; i++) {
final String s = parts[i];
internalVersion[i] = Integer.parseInt(s);
}
}
/**
* Creates a comparable version based on only numbers
*/
public
Version(String... version) {
if (version == null) {
throw new IllegalArgumentException("Version can not be null");
}
int length = version.length;
if (length > 3) {
throw new IllegalArgumentException("Invalid version format");
}
this.build = null;
this.isBeta = false;
StringBuilder builder = new StringBuilder(length + 3);
internalVersion = new int[length];
for (int i = 0; i < length; i++) {
final String s = version[i];
if (!MathUtil.isInteger(s)) {
throw new IllegalArgumentException("Version must be a number");
}
internalVersion[i] = Integer.parseInt(s);
builder.append(s)
.append('.');
}
this.version = builder.toString();
}
/**
* Creates a comparable version based on only numbers
*/
public
Version(int... version) {
if (version == null) {
throw new IllegalArgumentException("Version can not be null");
}
if (version.length > 3) {
throw new IllegalArgumentException("Invalid version format");
}
this.build = null;
this.isBeta = false;
StringBuilder builder = new StringBuilder(version.length + 3);
internalVersion = new int[version.length];
for (int i = 0; i < version.length; i++) {
internalVersion[i] = version[i];
builder.append(i)
.append('.');
}
this.version = builder.toString();
}
/**
* Converts a version into a "beta" version, without any additional build information
* <p>
* BETA+BUILD < BETA < STABLE+BUILD < STABLE.
* Stable is a version that is exclusively numbers. Builds are always equal, even if a different build commit hash/etc.
*/
public
Version beta() {
return new Version(version, true, build);
}
/**
* Creates a version with specific build information (such as sha commit hash, etc)
* <p>
* BETA+BUILD < BETA < STABLE+BUILD < STABLE.
* Stable is a version that is exclusively numbers. Builds are always equal, even if a different build commit hash/etc.
*/
public
Version build(String build) {
return new Version(version, isBeta, build);
}
public
boolean isGreater(Object that) {
if (this == that) {
return false;
}
if (that == null) {
return true;
}
if (this.getClass() != that.getClass()) {
return false;
}
return this.compareTo((Version) that) > 0;
}
public
boolean isGreaterOrEquals(Object that) {
if (this == that) {
return true;
}
if (that == null) {
return true;
}
if (this.getClass() != that.getClass()) {
return false;
}
return this.compareTo((Version) that) >= 0;
}
public
boolean isLess(Object that) {
if (this == that) {
return false;
}
if (that == null) {
return false;
}
if (this.getClass() != that.getClass()) {
return false;
}
return this.compareTo((Version) that) < 0;
}
public
boolean isLessOrEquals(Object that) {
if (this == that) {
return true;
}
if (that == null) {
return false;
}
if (this.getClass() != that.getClass()) {
return false;
}
return this.compareTo((Version) that) <= 0;
}
public
boolean isEquals(Object that) {
return equals(that);
}
@Override
public
boolean equals(Object that) {
if (this == that) {
return true;
}
if (that == null) {
return false;
}
if (this.getClass() != that.getClass()) {
return false;
}
return this.compareTo((Version) that) == 0;
}
@Override
public
int compareTo(Version that) {
if (that == null) {
return 1;
}
int[] thisParts = this.internalVersion;
int[] thatParts = that.internalVersion;
int maxLength = Math.max(thisParts.length, thatParts.length);
for (int i = 0; i < maxLength; i++) {
int thisPart;
if (i < thisParts.length) {
thisPart = thisParts[i];
}
else {
thisPart = 0;
}
int thatPart;
if (i < thatParts.length) {
thatPart = thatParts[i];
}
else {
thatPart = 0;
}
if (thisPart < thatPart) {
return -1;
}
if (thisPart > thatPart) {
return 1;
}
}
// our numbers are all equal, now determine equality based on BETA/BUILD info
// BETA+BUILD < BETA < STABLE+BUILD < STABLE.
// Stable is a version that is exclusively numbers. Builds are always equal, even if a different build commit hash/etc.
if (this.isBeta) {
if (this.build != null) {
if (that.isBeta) {
if (that.build != null) {
// BETA+BUILD == BETA+BUILD
return 0;
}
// BETA+BUILD < BETA
return -1;
}
// BETA+BUILD < STABLE+BUILD < STABLE
return -1;
}
else {
if (that.isBeta) {
if (that.build != null) {
// BETA > BETA+BUILD
return 1;
}
// BETA == BETA
return 0;
}
// BETA < STABLE+BUILD < STABLE
return -1;
}
}
// else this is STABLE or STABLE+BUILD
if (this.build != null) {
if (that.isBeta) {
// STABLE+BUILD > BETA > BETA+BUILD
return 1;
}
if (that.build != null) {
// STABLE+BUILD == STABLE+BUILD
return 0;
}
// STABLE+BUILD < STABLE
return -1;
}
else {
if (that.isBeta) {
// STABLE > BETA > BETA+BUILD
return 1;
}
if (that.build != null) {
// STABLE > STABLE+BUILD
return 1;
}
// STABLE == STABLE
return 0;
}
}
@Override
public final
int hashCode() {
// better hashing than just using .toString().hashCode()
int hashCode = 0;
for (int i = 0; i < internalVersion.length; i++) {
final int part = internalVersion[i];
if (part > 0) {
hashCode += PRIME[i] ^ part;
}
}
if (build != null) {
hashCode += build.hashCode();
}
return hashCode;
}
@Override
public
String toString() {
if (isBeta) {
if (build != null) {
return version + "-BETA+" + build;
}
return version + "-BETA";
}
if (build != null) {
return version + "+" + build;
}
return version;
}
}