+ * Based on:
+ * https://msdn.microsoft.com/en-us/library/system.version(v=vs.110).aspx Based
+ * on:
+ * https://github.com/mono/mono/blob/master/mcs/class/referencesource/mscorlib/system/version.cs
+ */
+public final class Version implements Comparable
+ * A string representation of a Version must follow the regex:
+ * ([0-9]+\.){1-3}[0-9]+ without any numbers in it exceeding
+ * {@link Integer#MAX_VALUE}
+ *
+ * For a simpler representation format: major.minor[.build[.revision]]
+ *
+ * @param input The String representation of a Version
+ * @return a Version
+ * @throws IllegalArgumentException if the input does not follow a Version
+ * syntax.
+ * @throws NullPointerException if input is null
+ */
+ public static Version parseVersion(String input)
+ {
+ int length = input.length();
+ int[] result = new int[]{-1, -1, -1, -1};
+ int index = 0;
+ int currentNumber = -1;
+ int resultIndex = 0;
+
+ while (index < length)
+ {
+ if (resultIndex == 4)
+ {
+
+ throw new IllegalArgumentException("Found a version component beyond the fourth at " + input + "[" + index + "]");
+ }
+
+ char c = input.charAt(index);
+
+ // the ascii code for the character '0' is 0x30.
+ // used in favor of Character.digit(char, int) for performance.
+ int digit = c - 0x30;
+
+ if (c == '.')
+ {
+ if (currentNumber < 0)
+ {
+ throw new IllegalArgumentException("Expected a digit at " + input + "[" + index + "], but found " + c);
+ }
+
+ result[resultIndex] = currentNumber;
+ resultIndex++;
+ currentNumber = -1;
+ } else if (!(0 <= digit && digit <= 9))
+ {
+ throw new IllegalArgumentException("Version components must be separated by a '.', but found " + c + " at " + input + "[" + index + "]");
+ } else if (currentNumber < 0)
+ {
+ //Got digit
+ currentNumber = digit;
+ } else
+ {
+ // any digit subsequent to the first
+ if (currentNumber == 0)
+ {
+ // ensure we don't have a component that started with 0 and has any additional digits.
+ // The first 0 would be obsolete to represent its value, meaning that there would be multiple valid string representations for the same Version
+ throw new IllegalArgumentException("Unneeded 0 in version component number is illegal at " + input + "[" + index + "]");
+ }
+
+ //Got digit
+ currentNumber = currentNumber * 10 + digit;
+ }
+
+ index++;
+ }
+
+ if (currentNumber >= 0)
+ {
+ result[resultIndex] = currentNumber;
+ resultIndex++;
+ }
+
+ switch (resultIndex)
+ {
+ default:
+ throw new IllegalArgumentException("Version requires major and minor value: " + input);
+ case 2:
+ return new Version(result[0], result[1]);
+ case 3:
+ return new Version(result[0], result[1], result[2]);
+ case 4:
+ return new Version(result[0], result[1], result[2], result[3]);
+ }
+ }
+
+ /**
+ * Initializes a new instance of the Version class with the specified major,
+ * minor, build, and revision numbers.
+ *
+ * @param major major value
+ * @param minor minor value
+ * @param build build value
+ * @param revision revision value
+ * @throws IllegalArgumentException if major, minor, build or revision is
+ * less than 0
+ */
+ public Version(int major, int minor, int build, int revision)
+ {
+ this.major = validateRange("major", major);
+ this.minor = validateRange("minor", minor);
+ this.build = validateRange("build", build);
+ this.revision = validateRange("revision", revision);
+ }
+
+ /**
+ * Tries to convert the string representation of a version number to an
+ * equivalent Version object, and returns the result value or null if parse
+ * failed.
+ *
+ * @param input The input to parse
+ * @return The parsed version or null if parse failed.
+ */
+ public static Version tryParse(String input)
+ {
+ try
+ {
+ return parseVersion(input);
+ } catch (Exception ex)
+ {
+ //Simply ignore the error
+ return null;
+ }
+ }
+
+ /**
+ * Gets the value of the major component of the version number for the
+ * current Version object.
+ *
+ * @return major value
+ */
+ public int getMajor()
+ {
+ return major;
+ }
+
+ /**
+ * Gets the value of the minor component of the version number for the
+ * current Version object.
+ *
+ * @return minor value
+ */
+ public int getMinor()
+ {
+ return minor;
+ }
+
+ /**
+ * Gets the value of the build component of the version number for the
+ * current Version object.
+ *
+ * @return build value, -1 if undefined
+ */
+ public int getBuild()
+ {
+ return build;
+ }
+
+ /**
+ * Gets the value of the revision component of the version number for the
+ * current Version object.
+ *
+ * @return revision value, -1 if undefined
+ */
+ public int getRevision()
+ {
+ return revision;
+ }
+
+ /**
+ * Gets the high 16 bits of the revision number.
+ *
+ * @return major revision value, or -1 if revision is undefined
+ */
+ public int getMajorRevision()
+ {
+ return revision == -1 ? -1 : revision >>> 16 & 0xFFFF;
+ }
+
+ /**
+ * Gets the low 16 bits of the revision number.
+ *
+ * @return minor revision value, or -1 if revision is undefined
+ */
+ public int getMinorRevision()
+ {
+ return revision == -1 ? -1 : revision & 0xFFFF;
+ }
+
+ /**
+ * Compares the current Version object to a specified Version object and
+ * returns an indication of their relative values.
+ *
+ * @param other The version object to compare to
+ * @return 1 if this version is later than other, 0 if this version is equal
+ * to other, and -1 otherwise
+ * @throws NullPointerException if other is null
+ */
+ @Override
+ public int compareTo(Version other)
+ {
+ if (other == null)
+ {
+ return 1;
+ }
+
+ if (this.major != other.major)
+ {
+ return this.major > other.major ? 1 : -1;
+ }
+ if (this.minor != other.minor)
+ {
+ return this.minor > other.minor ? 1 : -1;
+ }
+ if (this.build != other.build)
+ {
+ return this.build > other.build ? 1 : -1;
+ }
+ if (this.revision != other.revision)
+ {
+ return this.revision > other.revision ? 1 : -1;
+ }
+ return 0;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ // rotate each component to start from a different byte
+ int major = this.major;
+ int minor = (this.minor >>> 8 | this.minor << 24);
+ int build = (this.build >>> 16 | this.build << 16);
+ int revision = (this.revision >>> 24 | this.revision << 8);
+ return major ^ minor ^ build ^ revision;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+
+ if (o == null || !(o instanceof Version))
+ {
+ return false;
+ }
+
+ Version version = (Version) o;
+
+ return major == version.major
+ && minor == version.minor
+ && build == version.build
+ && revision == version.revision;
+ }
+
+ /**
+ * @return a new Version object whose value is the same as the current
+ * Version object.
+ */
+ @Override
+ public Version clone()
+ {
+ try
+ {
+ return (Version) super.clone();
+ } catch (Exception ex)
+ {
+ throw new InternalError("This should never happen", ex);
+ }
+ }
+
+ /**
+ * Gets a String representation of this Version object. The String
+ * representation of two distinct Version objects is equal if and only if
+ * the two Version objects are equal as declared by
+ * {@link Object#equals(Object)} The returned representation includes at
+ * least 1 component of this version, but will include any components that
+ * are in use.
+ *
+ * @return a String representation of this Version object.
+ */
+ @Override
+ public String toString()
+ {
+ return toString(getComponentCount());
+ }
+
+ /**
+ * Converts the value of the current Version object to its equivalent String
+ * representation. A specified count indicates the number of components to
+ * return.
+ *
+ * @param components the number of components to include in the string
+ * representation of this Version object.
+ * @return A string representation of this version object.
+ * @throws IllegalArgumentException if components is not between 0 and 4
+ * inclusive, or components exceeds {@link #getComponentCount()}
+ */
+ public String toString(int components)
+ {
+ if (!(0 <= components && components <= 4))
+ {
+ throw new IllegalArgumentException();
+ }
+ StringBuilder result = new StringBuilder();
+ if (0 < components)
+ {
+ result.append(major);
+ }
+ if (1 < components)
+ {
+ result.append('.').append(minor);
+ }
+ if (2 < components)
+ {
+ result.append('.').append(build);
+ }
+ if (3 < components)
+ {
+ result.append('.').append(revision);
+ }
+ return result.toString();
+ }
+
+ /**
+ * Computes the amount of used components. That is, the number of components
+ * counted up to and including the last defined component.
+ *
+ * @return the amount of used components
+ */
+ public int getComponentCount()
+ {
+ if (revision != -1)
+ {
+ return 4;
+ }
+ if (build != -1)
+ {
+ return 3;
+ }
+ return 2;
+ }
+ /*
+ / **
+ * Tries to convert the string representation of a version number to an
+ * equivalent Version object, and returns a value that indicates whether the
+ * conversion succeeded.
+ *
+ * @param input The input to parse
+ * @param version The result version
+ * @return true if the input was parsed successfully and the result of
+ * parsing the input is equal to the given version
+ * @throws NullPointerException if input is null or version is null
+ * /
+ public static boolean tryParse(String input, InOutParam