summaryrefslogtreecommitdiff
path: root/src/main/java/sevenUnits/SemanticVersionNumber.java
diff options
context:
space:
mode:
authorAdrien Hopkins <ahopk127@my.yorku.ca>2022-02-24 16:44:13 -0500
committerAdrien Hopkins <ahopk127@my.yorku.ca>2022-02-24 16:44:13 -0500
commitb7eee33a5b162b4057d04d28f45738e3048bf01d (patch)
tree3b99cc019fac82fb5813e4e14a0d710e9cbf3913 /src/main/java/sevenUnits/SemanticVersionNumber.java
parent63740b955b5baf955cac4f720a4c75f576d645f4 (diff)
Moved SemanticVersionNumber to sevenUnits.utils
Diffstat (limited to 'src/main/java/sevenUnits/SemanticVersionNumber.java')
-rw-r--r--src/main/java/sevenUnits/SemanticVersionNumber.java691
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;
- }
-}