package net.vhati.modmanager.core;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A version string (eg, 10.4.2_17 or 2.7.5rc1 ).<br>
* <br>
* It is composed of three parts:<br>
* - A series of period-separated positive ints.<br>
*
* - The numbers may be immediately followed by a short
* suffix string.<br>
*
* - Finally, a string comment, separated from the rest
* by a space.<br>
* <br>
* The (numbers + suffix) or comment may appear alone.<br>
* <br>
* For details, see the string constructor and compareTo().
*/
public class ComparableVersion implements Comparable<ComparableVersion> {
private Pattern numbersPtn = Pattern.compile("^((?:\\d+[.])*\\d+)");
private Pattern suffixPtn = Pattern.compile("([-_]|(?:[-_]?(?:[ab]|r|rc)))(\\d+)|([A-Za-z](?= |$))");
private Pattern commentPtn = Pattern.compile("(.+)$");
private int[] numbers;
private String suffix;
private String comment;
private String suffixDivider; // Suffix prior to a number, if there was a number.
private int suffixNum;
public ComparableVersion(int[] numbers, String suffix, String comment) {
this.numbers = numbers;
setSuffix(suffix);
setComment(comment);
}
/**
* Constructs an AppVersion by parsing a string.
*
* The suffix can be:<br>
* - A divider string followed by a number.<br>
* - Optional Hyphen/underscore, then a|b|r|rc, then 0-9+.<br>
* - Hyphen/underscore, then 0-9+.<br>
* Or the suffix can be a single letter without a number.<br>
* <br>
* Examples:
*
* <pre>
* 1
* 1 Blah
* 1.2 Blah
* 1.2.3 Blah
* 1.2.3-8 Blah
* 1.2.3_b9 Blah
* 1.2.3a1 Blah
* 1.2.3b1 Blah
* 1.2.3rc2 Blah
* 1.2.3z Blah
* 1.2.3D
* Alpha
* </pre>
*
* @throws IllegalArgumentException
* if the string is unsuitable
*/
public ComparableVersion(String s) {
boolean noNumbers = true;
boolean noComment = true;
Matcher numbersMatcher = numbersPtn.matcher(s);
Matcher suffixMatcher = suffixPtn.matcher(s);
Matcher commentMatcher = commentPtn.matcher(s);
if (numbersMatcher.lookingAt()) {
noNumbers = false;
setNumbers(numbersMatcher.group(0));
commentMatcher.region(numbersMatcher.end(), s.length());
// We have numbers; do we have a suffix?
suffixMatcher.region(numbersMatcher.end(), s.length());
if (suffixMatcher.lookingAt()) {
setSuffix(suffixMatcher.group(0));
commentMatcher.region(suffixMatcher.end(), s.length());
}
else {
setSuffix(null);
}
// If a space occurs after (numbers +suffix?), skip it.
// Thus the comment matcher will start on the first comment char.
//
if (commentMatcher.regionStart() + 1 < s.length()) {
if (s.charAt(commentMatcher.regionStart()) == ' ') {
commentMatcher.region(commentMatcher.regionStart() + 1, s.length());
}
}
}
else {
numbers = new int[0];
setSuffix(null);
}
// Check for a comment (at the start, elsewhere if region was set).
if (commentMatcher.lookingAt()) {
noComment = false;
setComment(commentMatcher.group(1));
}
if (noNumbers && noComment) {
throw new IllegalArgumentException("Could not parse version string: " + s);
}
}
private void setNumbers(String s) {
if (s == null || s.length() == 0) {
numbers = new int[0];
return;
}
Matcher m = numbersPtn.matcher(s);
if (m.matches()) {
String numString = m.group(1);
String[] numChunks = numString.split("[.]");
numbers = new int[numChunks.length];
for (int i = 0; i < numChunks.length; i++) {
numbers[i] = Integer.parseInt(numChunks[i]);
}
}
else {
throw new IllegalArgumentException("Could not parse version numbers string: " + s);
}
}
private void setSuffix(String s) {
if (s == null || s.length() == 0) {
suffix = null;
suffixNum = -1;
return;
}
Matcher m = suffixPtn.matcher(s);
if (m.matches()) {
suffix = s;
// Matched groups 1 and 2... or 3.
if (m.group(1) != null) {
suffixDivider = m.group(1);
}
if (m.group(2) != null) {
suffixNum = Integer.parseInt(m.group(2));
} else {
suffixNum = -1;
}
suffix = m.group(0);
}
else {
throw new IllegalArgumentException("Could not parse version suffix string: " + s);
}
}
private void setComment(String s) {
if (s == null || s.length() == 0) {
comment = null;
return;
}
Matcher m = commentPtn.matcher(s);
if (m.matches()) {
comment = m.group(1);
}
else {
throw new IllegalArgumentException("Could not parse version comment string: " + s);
}
}
/**
* Returns the array of major/minor/etc version numbers.
*/
public int[] getNumbers() {
return numbers;
}
/**
* Returns the pre-number portion of the suffix, or null if there was no number.
*/
public String getSuffixDivider() {
return suffixDivider;
}
/**
* Returns the number in the suffix, or -1 if there was no number.
*/
public int getSuffixNumber() {
return suffixNum;
}
/**
* Returns the entire suffix, or null.
*/
public String getSuffix() {
return suffix;
}
/**
* Returns the human-readable comment, or null.
*/
public String getComment() {
return comment;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
for (int number : numbers) {
if (buf.length() > 0)
buf.append(".");
buf.append(number);
}
if (suffix != null) {
buf.append(suffix);
}
if (comment != null) {
if (buf.length() > 0)
buf.append(" ");
buf.append(comment);
}
return buf.toString();
}
/**
* Compares this object with the specified object for order.<br>
* <br>
* - The ints are compared arithmetically. In case of ties,
* the version with the most numbers wins.<br>
* - If both versions' suffixes have a number, and the same
* characters appear before that number, then the suffix number
* is compared arithmetically.<br>
* - Then the entire suffix is compared alphabetically.<br>
* - Then the comment is compared alphabetically.
*/
@Override
public int compareTo(ComparableVersion other) {
if (other == null)
return -1;
if (other == this)
return 0;
int[] oNumbers = other.getNumbers();
for (int i = 0; i < numbers.length && i < oNumbers.length; i++) {
if (numbers[i] < oNumbers[i])
return -1;
if (numbers[i] > oNumbers[i])
return 1;
}
if (numbers.length < oNumbers.length)
return -1;
if (numbers.length > oNumbers.length)
return 1;
if (suffixDivider != null && other.getSuffixDivider() != null) {
if (suffixDivider.equals(other.getSuffixDivider())) {
if (suffixNum < other.getSuffixNumber())
return -1;
if (suffixNum > other.getSuffixNumber())
return 1;
}
}
if (suffix == null && other.getSuffix() != null)
return -1;
if (suffix != null && other.getSuffix() == null)
return 1;
if (suffix != null && other.getSuffix() != null) {
int cmp = suffix.compareTo(other.getSuffix());
if (cmp != 0)
return cmp;
}
if (comment == null && other.getComment() != null)
return -1;
if (comment != null && other.getComment() == null)
return 1;
if (comment != null && other.getComment() != null) {
int cmp = comment.compareTo(other.getComment());
if (cmp != 0)
return cmp;
}
return 0;
}
@Override
public boolean equals(Object o) {
if (o == null)
return false;
if (o == this)
return true;
if (o instanceof ComparableVersion == false)
return false;
ComparableVersion other = (ComparableVersion) o;
int[] oNumbers = other.getNumbers();
for (int i = 0; i < numbers.length && i < oNumbers.length; i++) {
if (numbers[i] != oNumbers[i])
return false;
}
if (numbers.length != oNumbers.length)
return false;
if (suffix == null && other.getSuffix() != null)
return false;
if (suffix != null && other.getSuffix() == null)
return false;
if (!suffix.equals(other.getSuffix()))
return false;
if (comment == null && other.getComment() != null)
return false;
if (comment != null && other.getComment() == null)
return false;
if (!comment.equals(other.getComment()))
return false;
return true;
}
@Override
public int hashCode() {
int result = 79;
int salt = 35;
int nullCode = 13;
List<Integer> tmpNumbers = new ArrayList<Integer>(getNumbers().length);
for (int n : getNumbers())
tmpNumbers.add(new Integer(n));
result = salt * result + tmpNumbers.hashCode();
String tmpSuffix = getSuffix();
if (tmpSuffix == null)
result = salt * result + nullCode;
else
result = salt * result + tmpSuffix.hashCode();
String tmpComment = getComment();
if (tmpComment == null)
result = salt * result + nullCode;
else
result = salt * result + tmpComment.hashCode();
return result;
}
}