All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.indeed.util.core.ReleaseVersion Maven / Gradle / Ivy

There is a newer version: 1.0.52-3042601
Show newest version
package com.indeed.util.core;

import javax.annotation.Nullable;
import java.util.Arrays;

/**
 * Represents a multi-part version number internally as a 64-bit integer for fast
 * comparison. Can represent a numeric version number with up to four components:
 * majorVersion.minorVersion.patchVersion.buildNumber.
 * 
  • *
      majorVersion can be a positive integer between 0 and 32767 (inclusive)
    *
      minorVersion can be a positive integer between 0 and 65535 (inclusive)
    *
      patchVersion can be a positive integer between 0 and 65535 (inclusive)
    *
      buildNumber can be a positive integer between 0 and 65535 (inclusive)
    *
  • * *

    * Supports comparison using wild-card expressions, using the special character 'x'. The wild card * character can only be using at the end of a version string. * * assert ReleaseVersion.fromString("1.0").compareTo(ReleaseVersion.fromString("1.1.x") == 1; * assert ReleaseVersion.fromString("1.0.x").compareTo(ReleaseVersion.fromString("1.0.0.1") == 0; * assert ReleaseVersion.fromString("1.0.0.1").compareTo(ReleaseVersion.fromString("1.0.x") == 0; * assert ReleaseVersion.fromString("1.0.x").compareTo(ReleaseVersion.fromString("1.0.0.x") == 0; * *

    * *

    * Supports interpreting a version string. The version must be fully qualified (e.g. "1.0.0.0") * or end with a wild card ("x") to be parsed correctly. *

    * *

    * When using the Builder, you can use the wild card behavior by calling setMatchPrecision with the * lowest level to use in comparison. Default match matchPrecision is BUILD. * * ReleaseVersion lhs = newBuilder().setMajorVersion(1).build(); // equivalent to fromString("1.0.0.0") * ReleaseVersion rhs = newBuilder().setMajorVersion(1).setMinorVersion(1).build(); // equivalent to fromString("1.1.0.0") * assert lhs.compareTo(rhs) == -1; * lhs = newBuilder().setMajorVersion(1).setMatchPrecision(MAJOR).build(); // equivalent to fromString("1.x") * assert lhs.compareTo(rhs) == 0; * *

    * * @author [email protected] (Jack Humphrey) */ public class ReleaseVersion implements Comparable { private static final char WILDCARD = 'x'; private static boolean isWildcard(@Nullable String versionComponent) { return versionComponent != null && versionComponent.length() == 1 && WILDCARD == versionComponent.charAt(0); } private final long version; private final MatchPrecision matchPrecision; public enum MatchPrecision { MAJOR(1, 0x7FFF000000000000L), MINOR(2, 0x7FFFFFFF00000000L), PATCH(3, 0x7FFFFFFFFFFF0000L), BUILD(4, 0x7FFFFFFFFFFFFFFFL); int length; long mask; MatchPrecision(int length, long mask) { this.length = length; this.mask = mask; } static MatchPrecision forLength(int length) { switch (length) { case 0: case 1: return MAJOR; case 2: return MINOR; case 3: return PATCH; case 4: return BUILD; } return null; } } private ReleaseVersion(short majorVersion, int minorVersion, int patchVersion, int buildNumber, MatchPrecision matchPrecision) { version = ((long)majorVersion << 48) | ((long)minorVersion << 32) | ((long)patchVersion << 16) | (long)buildNumber; this.matchPrecision = matchPrecision; } public MatchPrecision getMatchPrecision() { return matchPrecision; } @Override public String toString() { final short majorVersion = (short) ((version >> 48) & 0x7FFF); final int minorVersion = (int) (version >> 32) & 0xFFFF; final int patchVersion = (int) (version >> 16) & 0xFFFF; final int buildNumber = (int) (version & 0xFFFF); final StringBuilder b = new StringBuilder(); b.append(Short.toString(majorVersion)).append('.'); if (matchPrecision == MatchPrecision.MAJOR) { b.append(WILDCARD); } else { b.append(Integer.toString(minorVersion)).append('.'); if (matchPrecision == MatchPrecision.MINOR) { b.append(WILDCARD); } else { b.append(Integer.toString(patchVersion)).append('.'); if (matchPrecision == MatchPrecision.PATCH) { b.append(WILDCARD); } else { b.append(Integer.toString(buildNumber)); } } } return b.toString(); } @Override public int compareTo(ReleaseVersion other) { final MatchPrecision minPrecision = MatchPrecision.forLength(Math.min(matchPrecision.length, other.matchPrecision.length)); final long lhs = version & minPrecision.mask; final long rhs = other.version & minPrecision.mask; return (lhs < rhs ? -1 : (lhs == rhs ? 0 : 1)); } @Override public boolean equals(final Object o) { if (o instanceof ReleaseVersion) { return compareTo((ReleaseVersion) o) == 0; } return false; } /** * Turn a version string into an object. Enforces strict interpretation: string must either * be fully qualified (x.x.x.x) or use a wildcard (like 3.2.x) or an IllegalArgumentException will * be thrown. All numbers must parse as integers or a NumberFormatException will be thrown. * This method is typically for use in code when dealing with explicit string literals. * @param versionString fully-qualified or wild-card version string * @throws java.lang.IllegalArgumentException if version string is not fully-qualified or a wild card, or if a * version number does not parse as an integer */ public static ReleaseVersion fromString(String versionString) { return new Builder().fromString(versionString, false).build(); } /** * Turn a version string into an object. Does lenient parsing: "1.2.rc1" will turn into "1.2.0.0", for * example. Will not throw {@link java.lang.IllegalArgumentException}. */ public static ReleaseVersion fromStringSafely(String versionString, ReleaseVersion defaultVersion) { try { return new Builder().fromString(versionString, true).build(); } catch (Exception e) { return defaultVersion; } } public static Builder newBuilder() { return new Builder(); } public static final int MAX_MAJOR_VERSION = Short.MAX_VALUE; public static final int MAX_MINOR_VERSION = (Short.MAX_VALUE << 1) + 1; public static final int MAX_PATCH_VERSION = (Short.MAX_VALUE << 1) + 1; public static final int MAX_BUILD_NUMBER = (Short.MAX_VALUE << 1) + 1; public final static class Builder { private short majorVersion; private int minorVersion; private int patchVersion; private int buildNumber; private MatchPrecision matchPrecision = MatchPrecision.BUILD; public Builder setMatchPrecision(MatchPrecision matchPrecision) { this.matchPrecision = matchPrecision; return Builder.this; } public Builder setMajorVersion(int majorVersion) { if (majorVersion > MAX_MAJOR_VERSION || majorVersion < 0) { throw new IllegalArgumentException("invalid major version " + majorVersion); } this.majorVersion = (short) majorVersion; return Builder.this; } public Builder setMinorVersion(int minorVersion) { if (minorVersion > MAX_MINOR_VERSION || minorVersion < 0) { throw new IllegalArgumentException("invalid minor version " + minorVersion); } this.minorVersion = minorVersion; return Builder.this; } public Builder setPatchVersion(int patchVersion) { if (patchVersion > MAX_PATCH_VERSION || patchVersion < 0) { throw new IllegalArgumentException("invalid patch version " + patchVersion); } this.patchVersion = patchVersion; return Builder.this; } public Builder setBuildNumber(int buildNumber) { if (buildNumber > MAX_BUILD_NUMBER || buildNumber < 0) { throw new IllegalArgumentException("invalid build number " + buildNumber); } this.buildNumber = buildNumber; return Builder.this; } /** * @param versionString * @param lenient if false, will throw exceptions if not proper version string * @throws java.lang.IllegalArgumentException if version string is invalid and !lenient (if it can't * parse a major version, will throw this exception even if lenient. */ public Builder fromString(String versionString, boolean lenient) { String[] parts = versionString.split("\\."); if (parts.length > 0 && isWildcard(parts[parts.length - 1])) { parts = Arrays.copyOfRange(parts, 0, parts.length - 1); if (!lenient && parts.length == 0) { throw new IllegalArgumentException("Invalid version string: " + versionString); } } else if (!lenient && parts.length < 4) { // Will allow parts.length > 4 and discard the extra parts throw new IllegalArgumentException("ReleaseVersion string must have 4 numbers or end in " + WILDCARD + ": " + versionString); } // this part can't be lenient setMajorVersion(parts.length > 0 ? Integer.parseInt(parts[0]) : 0); try { setMinorVersion(parts.length > 1 ? Integer.parseInt(parts[1]) : 0); setPatchVersion(parts.length > 2 ? Integer.parseInt(parts[2]) : 0); setBuildNumber(parts.length > 3 ? Integer.parseInt(parts[3]) : 0); } catch (IllegalArgumentException e) { if (!lenient) { throw e; } } if (lenient) { // when lenient, we assume full precision setMatchPrecision(MatchPrecision.BUILD); } else { setMatchPrecision(MatchPrecision.forLength(Math.min(parts.length, 4))); } return Builder.this; } public ReleaseVersion build() { return new ReleaseVersion(majorVersion, minorVersion, patchVersion, buildNumber, matchPrecision); } }}




    © 2015 - 2024 Weber Informatics LLC | Privacy Policy