diff options
author | Adrien Hopkins <ahopk127@my.yorku.ca> | 2022-02-24 16:44:13 -0500 |
---|---|---|
committer | Adrien Hopkins <ahopk127@my.yorku.ca> | 2022-02-24 16:44:13 -0500 |
commit | b7eee33a5b162b4057d04d28f45738e3048bf01d (patch) | |
tree | 3b99cc019fac82fb5813e4e14a0d710e9cbf3913 /src/main/java/sevenUnits/SemanticVersionNumber.java | |
parent | 63740b955b5baf955cac4f720a4c75f576d645f4 (diff) |
Moved SemanticVersionNumber to sevenUnits.utils
Diffstat (limited to 'src/main/java/sevenUnits/SemanticVersionNumber.java')
-rw-r--r-- | src/main/java/sevenUnits/SemanticVersionNumber.java | 691 |
1 files changed, 0 insertions, 691 deletions
diff --git a/src/main/java/sevenUnits/SemanticVersionNumber.java b/src/main/java/sevenUnits/SemanticVersionNumber.java deleted file mode 100644 index 01aeb27..0000000 --- a/src/main/java/sevenUnits/SemanticVersionNumber.java +++ /dev/null @@ -1,691 +0,0 @@ -/** - * Copyright (C) 2022 Adrien Hopkins - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ -package sevenUnits; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * A version number in the <a href="https://semver.org">Semantic Versioning</a> - * scheme - * <p> - * Each version number has three main parts: - * <ol> - * <li>The major version, which increments when backwards incompatible changes - * are made - * <li>The minor version, which increments when backwards compatible feature - * changes are made - * <li>The patch version, which increments when backwards compatible bug fixes - * are made - * </ol> - * - * @since 2022-02-19 - */ -public final class SemanticVersionNumber - implements Comparable<SemanticVersionNumber> { - /** - * A builder that can be used to create complex version numbers. - * <p> - * Note: None of this builder's methods tolerate null arguments, arrays - * containing nulls, negative numbers, or non-alphanumeric identifiers. Nulls - * throw NullPointerExceptions, everything else throws - * IllegalArgumentException. - * - * @since 2022-02-19 - */ - public static final class Builder { - private final int major; - private final int minor; - private final int patch; - private final List<String> preReleaseIdentifiers; - private final List<String> buildMetadata; - - /** - * Creates a builder which can be used to create a - * {@code SemanticVersionNumber} - * - * @param major major version number of final version - * @param minor minor version number of final version - * @param patch patch version number of final version - * @since 2022-02-19 - */ - private Builder(int major, int minor, int patch) { - this.major = major; - this.minor = minor; - this.patch = patch; - this.preReleaseIdentifiers = new ArrayList<>(); - this.buildMetadata = new ArrayList<>(); - } - - /** - * @return version number created by this builder - * @since 2022-02-19 - */ - public SemanticVersionNumber build() { - return new SemanticVersionNumber(this.major, this.minor, this.patch, - this.preReleaseIdentifiers, this.buildMetadata); - } - - /** - * Adds one or more build metadata identifiers - * - * @param identifiers build metadata - * @return this builder - * @since 2022-02-19 - */ - public Builder buildMetadata(List<String> identifiers) { - Objects.requireNonNull(identifiers, "identifiers may not be null"); - for (final String identifier : identifiers) { - Objects.requireNonNull(identifier, "identifier may not be null"); - if (!VALID_IDENTIFIER.matcher(identifier).matches()) - throw new IllegalArgumentException( - String.format("Invalid identifier \"%s\"", identifier)); - this.buildMetadata.add(identifier); - } - return this; - } - - /** - * Adds one or more build metadata identifiers - * - * @param identifiers build metadata - * @return this builder - * @since 2022-02-19 - */ - public Builder buildMetadata(String... identifiers) { - Objects.requireNonNull(identifiers, "identifiers may not be null"); - for (final String identifier : identifiers) { - Objects.requireNonNull(identifier, "identifier may not be null"); - if (!VALID_IDENTIFIER.matcher(identifier).matches()) - throw new IllegalArgumentException( - String.format("Invalid identifier \"%s\"", identifier)); - this.buildMetadata.add(identifier); - } - return this; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (!(obj instanceof Builder)) - return false; - final Builder other = (Builder) obj; - return Objects.equals(this.buildMetadata, other.buildMetadata) - && this.major == other.major && this.minor == other.minor - && this.patch == other.patch && Objects.equals( - this.preReleaseIdentifiers, other.preReleaseIdentifiers); - } - - @Override - public int hashCode() { - return Objects.hash(this.buildMetadata, this.major, this.minor, - this.patch, this.preReleaseIdentifiers); - } - - /** - * Adds one or more numeric identifiers to the version number - * - * @param identifiers pre-release identifier(s) to add - * @return this builder - * @since 2022-02-19 - */ - public Builder preRelease(int... identifiers) { - Objects.requireNonNull(identifiers, "identifiers may not be null"); - for (final int identifier : identifiers) { - if (identifier < 0) - throw new IllegalArgumentException( - "Numeric identifiers may not be negative"); - this.preReleaseIdentifiers.add(Integer.toString(identifier)); - } - return this; - } - - /** - * Adds one or more pre-release identifier(s) to the version number - * - * @param identifiers pre-release identifier(s) to add - * @return this builder - * @since 2022-02-19 - */ - public Builder preRelease(List<String> identifiers) { - Objects.requireNonNull(identifiers, "identifiers may not be null"); - for (final String identifier : identifiers) { - Objects.requireNonNull(identifier, "identifier may not be null"); - if (!VALID_IDENTIFIER.matcher(identifier).matches()) - throw new IllegalArgumentException( - String.format("Invalid identifier \"%s\"", identifier)); - this.preReleaseIdentifiers.add(identifier); - } - return this; - } - - /** - * Adds one or more pre-release identifier(s) to the version number - * - * @param identifiers pre-release identifier(s) to add - * @return this builder - * @since 2022-02-19 - */ - public Builder preRelease(String... identifiers) { - Objects.requireNonNull(identifiers, "identifiers may not be null"); - for (final String identifier : identifiers) { - Objects.requireNonNull(identifier, "identifier may not be null"); - if (!VALID_IDENTIFIER.matcher(identifier).matches()) - throw new IllegalArgumentException( - String.format("Invalid identifier \"%s\"", identifier)); - this.preReleaseIdentifiers.add(identifier); - } - return this; - } - - /** - * Adds a string identifier and an integer identifer to pre-release data - * - * @param identifier1 first identifier - * @param identifier2 second identifier - * @return this builder - * @since 2022-02-19 - */ - public Builder preRelease(String identifier1, int identifier2) { - Objects.requireNonNull(identifier1, "identifier1 may not be null"); - if (!VALID_IDENTIFIER.matcher(identifier1).matches()) - throw new IllegalArgumentException( - String.format("Invalid identifier \"%s\"", identifier1)); - if (identifier2 < 0) - throw new IllegalArgumentException( - "Integer identifier cannot be negative"); - this.preReleaseIdentifiers.add(identifier1); - this.preReleaseIdentifiers.add(Integer.toString(identifier2)); - return this; - } - - @Override - public String toString() { - return "Semantic Version Builder: " + this.build().toString(); - } - } - - /** - * An alternative comparison method for version numbers. This uses the - * version's natural order, but the build metadata will be compared (using - * the same rules as pre-release identifiers) if everything else is equal. - * <p> - * This ordering is consistent with equals, unlike - * {@code SemanticVersionNumber}'s natural ordering. - */ - public static final Comparator<SemanticVersionNumber> BUILD_METADATA_COMPARATOR = new Comparator<>() { - @Override - public int compare(SemanticVersionNumber o1, SemanticVersionNumber o2) { - Objects.requireNonNull(o1, "o1 may not be null"); - Objects.requireNonNull(o2, "o2 may not be null"); - final int naturalComparison = o1.compareTo(o2); - if (naturalComparison == 0) - return SemanticVersionNumber.compare(o1.buildMetadata, - o2.buildMetadata); - else - return naturalComparison; - }; - }; - - /** The alphanumeric pattern all identifiers must follow */ - private static final Pattern VALID_IDENTIFIER = Pattern - .compile("[0-9A-Za-z-]+"); - - /** The numeric pattern which causes special behaviour */ - private static final Pattern NUMERIC_IDENTIFER = Pattern.compile("[0-9]+"); - - /** The pattern for a version number */ - private static final Pattern VERSION_NUMBER = Pattern - .compile("(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)" // main - // version - + "(?:-([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?" // pre-release - + "(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?"); // build data - - /** - * Creates a builder that can be used to create a version number - * - * @param major major version number of final version - * @param minor minor version number of final version - * @param patch patch version number of final version - * @return version number builder - * @throws IllegalArgumentException if any argument is negative - * @since 2022-02-19 - */ - public static final SemanticVersionNumber.Builder builder(int major, - int minor, int patch) { - if (major < 0) - throw new IllegalArgumentException( - "Major version must be non-negative."); - if (minor < 0) - throw new IllegalArgumentException( - "Minor version must be non-negative."); - if (patch < 0) - throw new IllegalArgumentException( - "Patch version must be non-negative."); - return new SemanticVersionNumber.Builder(major, minor, patch); - } - - /** - * Compares two lists of strings based on SemVer's precedence rules - * - * @param a first list - * @param b second list - * @return result of comparison as in a comparator - * @see Comparator - * @since 2022-02-20 - */ - private static final int compare(List<String> a, List<String> b) { - // test pre-release size - final int aSize = a.size(); - final int bSize = b.size(); - - // no identifiers is greater than any identifiers - if (aSize != 0 && bSize == 0) - return -1; - else if (aSize == 0 && bSize != 0) - return 1; - - // test identifiers one by one - for (int i = 0; i < Math.min(aSize, bSize); i++) { - final String aElement = a.get(i); - final String bElement = b.get(i); - - if (NUMERIC_IDENTIFER.matcher(aElement).matches()) { - if (NUMERIC_IDENTIFER.matcher(bElement).matches()) { - // both are numbers, compare them - final int aNumber = Integer.parseInt(aElement); - final int bNumber = Integer.parseInt(bElement); - - if (aNumber < bNumber) - return -1; - else if (aNumber > bNumber) - return 1; - } else - // aElement is a number and bElement is not a number - // by the rules, a goes before b - return -1; - } else { - if (NUMERIC_IDENTIFER.matcher(bElement).matches()) - // aElement is not a number but bElement is - // by the rules, a goes after b - return 1; - else { - // both are not numbers, compare them - final int comparison = aElement.compareTo(bElement); - if (comparison != 0) - return comparison; - } - } - } - - // we just tested the stuff that's in common, maybe someone has more - if (aSize < bSize) - return -1; - else if (aSize > bSize) - return 1; - else - return 0; - } - - /** - * Gets a version number from a string in the official format - * - * @param versionString string to parse - * @return {@code SemanticVersionNumber} instance - * @since 2022-02-19 - * @see {@link #toString} - */ - public static final SemanticVersionNumber fromString(String versionString) { - // parse & validate version string - Objects.requireNonNull(versionString, "versionString may not be null"); - final Matcher m = VERSION_NUMBER.matcher(versionString); - if (!m.matches()) - throw new IllegalArgumentException( - String.format("Provided string \"%s\" is not a version number", - versionString)); - - // main parts - final int major = Integer.parseInt(m.group(1)); - final int minor = Integer.parseInt(m.group(2)); - final int patch = Integer.parseInt(m.group(3)); - - // pre release - final List<String> preRelease; - if (m.group(4) == null) { - preRelease = List.of(); - } else { - preRelease = Arrays.asList(m.group(4).split("\\.")); - } - - // build metadata - final List<String> buildMetadata; - if (m.group(5) == null) { - buildMetadata = List.of(); - } else { - buildMetadata = Arrays.asList(m.group(5).split("\\.")); - } - - // return number - return new SemanticVersionNumber(major, minor, patch, preRelease, - buildMetadata); - } - - /** - * Tests whether a string is a valid Semantic Version string - * - * @param versionString string to test - * @return true iff string is valid - * @since 2022-02-19 - */ - public static final boolean isValidVersionString(String versionString) { - return VERSION_NUMBER.matcher(versionString).matches(); - } - - /** - * Creates a simple pre-release version number of the form - * MAJOR.MINOR.PATH-TYPE.NUMBER (e.g. 1.2.3-alpha.4). - * - * @param major major version number - * @param minor minor version number - * @param patch patch version number - * @param preReleaseType first pre-release element - * @param preReleaseNumber second pre-release element - * @return {@code SemanticVersionNumber} instance - * @throws IllegalArgumentException if any argument is negative or if the - * preReleaseType is null, empty or not - * alphanumeric (0-9, A-Z, a-z, - only) - * @since 2022-02-19 - */ - public static final SemanticVersionNumber preRelease(int major, int minor, - int patch, String preReleaseType, int preReleaseNumber) { - if (major < 0) - throw new IllegalArgumentException( - "Major version must be non-negative."); - if (minor < 0) - throw new IllegalArgumentException( - "Minor version must be non-negative."); - if (patch < 0) - throw new IllegalArgumentException( - "Patch version must be non-negative."); - Objects.requireNonNull(preReleaseType, "preReleaseType may not be null"); - if (!VALID_IDENTIFIER.matcher(preReleaseType).matches()) - throw new IllegalArgumentException( - String.format("Invalid identifier \"%s\".", preReleaseType)); - if (preReleaseNumber < 0) - throw new IllegalArgumentException( - "Pre-release number must be non-negative."); - return new SemanticVersionNumber(major, minor, patch, - List.of(preReleaseType, Integer.toString(preReleaseNumber)), - List.of()); - } - - /** - * Creates a {@code SemanticVersionNumber} instance without pre-release - * identifiers or build metadata. - * <p> - * Note: this method allows you to create versions with major version number - * 0, even though these versions would not be considered stable. - * - * @param major major version number - * @param minor minor version number - * @param patch patch version number - * @return {@code SemanticVersionNumber} instance - * @throws IllegalArgumentException if any argument is negative - * @since 2022-02-19 - */ - public static final SemanticVersionNumber stableVersion(int major, int minor, - int patch) { - if (major < 0) - throw new IllegalArgumentException( - "Major version must be non-negative."); - if (minor < 0) - throw new IllegalArgumentException( - "Minor version must be non-negative."); - if (patch < 0) - throw new IllegalArgumentException( - "Patch version must be non-negative."); - return new SemanticVersionNumber(major, minor, patch, List.of(), - List.of()); - } - - // parts of the version number - private final int major; - private final int minor; - private final int patch; - private final List<String> preReleaseIdentifiers; - private final List<String> buildMetadata; - - /** - * Creates a version number - * - * @param major major version number - * @param minor minor version number - * @param patch patch version number - * @param preReleaseIdentifiers pre-release version data - * @param buildMetadata build metadata - * @since 2022-02-19 - */ - private SemanticVersionNumber(int major, int minor, int patch, - List<String> preReleaseIdentifiers, List<String> buildMetadata) { - this.major = major; - this.minor = minor; - this.patch = patch; - this.preReleaseIdentifiers = preReleaseIdentifiers; - this.buildMetadata = buildMetadata; - } - - /** - * @return build metadata (empty if there is none) - * @since 2022-02-19 - */ - public List<String> buildMetadata() { - return Collections.unmodifiableList(this.buildMetadata); - } - - /** - * Compares two version numbers according to the official Semantic Versioning - * order. - * <p> - * Note: this ordering is not consistent with equals. Specifically, two - * versions that are identical except for their build metadata will be - * considered different by equals but the same by this method. This is - * required to follow the official Semantic Versioning specification. - * <p> - */ - @Override - public int compareTo(SemanticVersionNumber o) { - // test the three big numbers in order first - if (this.major < o.major) - return -1; - else if (this.major > o.major) - return 1; - - if (this.minor < o.minor) - return -1; - else if (this.minor > o.minor) - return 1; - - if (this.patch < o.patch) - return -1; - else if (this.patch > o.patch) - return 1; - - // now we just compare pre-release identifiers - // (remember: build metadata is ignored) - return SemanticVersionNumber.compare(this.preReleaseIdentifiers, - o.preReleaseIdentifiers); - } - - /** - * Determines the compatibility of code written for this version to - * {@code other}. More specifically: - * <p> - * If this function returns <b>true</b>, then there should be no problems - * upgrading code written for this version to version {@code other} as long - * as: - * <ul> - * <li>Semantic Versioning is being used properly - * <li>Your code doesn't depend on unintended features (if it does, it isn't - * necessarily compatible with any other version) - * </ul> - * If this function returns <b>false</b>, you may have to change your code to - * upgrade it to {@code other} - * - * <p> - * Two version numbers that are identical (ignoring build metadata) are - * always compatible. Different version numbers are compatible as long as: - * <ul> - * <li>The major version number is not 0 (if it is, the API is considered - * unstable and any upgrade can be backwards compatible) - * <li>The major version number is the same (changing the major version - * number implies bacwards incompatible changes) - * <li>This version comes before the other one in the official precedence - * order (downgrading can remove features you depend on) - * </ul> - * - * @param other version to compare with - * @return true if you can definitely upgrade to {@code other} without - * changing code - * @since 2022-02-20 - */ - public boolean compatibleWith(SemanticVersionNumber other) { - Objects.requireNonNull(other, "other may not be null"); - - return this.compareTo(other) == 0 || this.major != 0 - && this.major == other.major && this.compareTo(other) < 0; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (!(obj instanceof SemanticVersionNumber)) - return false; - final SemanticVersionNumber other = (SemanticVersionNumber) obj; - if (this.buildMetadata == null) { - if (other.buildMetadata != null) - return false; - } else if (!this.buildMetadata.equals(other.buildMetadata)) - return false; - if (this.major != other.major) - return false; - if (this.minor != other.minor) - return false; - if (this.patch != other.patch) - return false; - if (this.preReleaseIdentifiers == null) { - if (other.preReleaseIdentifiers != null) - return false; - } else if (!this.preReleaseIdentifiers - .equals(other.preReleaseIdentifiers)) - return false; - return true; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result - + (this.buildMetadata == null ? 0 : this.buildMetadata.hashCode()); - result = prime * result + this.major; - result = prime * result + this.minor; - result = prime * result + this.patch; - result = prime * result + (this.preReleaseIdentifiers == null ? 0 - : this.preReleaseIdentifiers.hashCode()); - return result; - } - - /** - * @return true iff this version is stable (major version > 0 and not a - * pre-release) - * @since 2022-02-19 - */ - public boolean isStable() { - return this.major > 0 && this.preReleaseIdentifiers.isEmpty(); - } - - /** - * @return the MAJOR version number, incremented when you make backwards - * incompatible API changes - * @since 2022-02-19 - */ - public int majorVersion() { - return this.major; - } - - /** - * @return the MINOR version number, incremented when you add backwards - * compatible functionality - * @since 2022-02-19 - */ - public int minorVersion() { - return this.minor; - } - - /** - * @return the PATCH version number, incremented when you make backwards - * compatible bug fixes - * @since 2022-02-19 - */ - public int patchVersion() { - return this.patch; - } - - /** - * @return identifiers describing this pre-release (empty if not a - * pre-release) - * @since 2022-02-19 - */ - public List<String> preReleaseIdentifiers() { - return Collections.unmodifiableList(this.preReleaseIdentifiers); - } - - /** - * Converts a version number to a string using the official SemVer format. - * The core of a version is MAJOR.MINOR.PATCH, without zero-padding. If - * pre-release identifiers are present, they are separated by periods and - * added after a '-'. If build metadata is present, it is separated by - * periods and added after a '+'. Pre-release identifiers go before version - * metadata. - * <p> - * For example, the version with major number 3, minor number 2, patch number - * 1, pre-release identifiers "alpha" and "1" and build metadata "2022-02-19" - * has a string representation "3.2.1-alpha.1+2022-02-19". - * - * @see <a href="https://semver.org">The official SemVer specification</a> - */ - @Override - public String toString() { - String versionString = String.format("%d.%d.%d", this.major, this.minor, - this.patch); - if (!this.preReleaseIdentifiers.isEmpty()) { - versionString += "-" + String.join(".", this.preReleaseIdentifiers); - } - if (!this.buildMetadata.isEmpty()) { - versionString += "+" + String.join(".", this.buildMetadata); - } - return versionString; - } -} |