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 ).
*
* It is composed of three parts:
* - A series of period-separated positive ints.
*
* - The numbers may be immediately followed by a short
* suffix string.
*
* - Finally, a string comment, separated from the rest
* by a space.
*
* The (numbers + suffix) or comment may appear alone.
*
* 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:
* - A divider string followed by a number.
* - Optional Hyphen/underscore, then a|b|r|rc, then 0-9+.
* - Hyphen/underscore, then 0-9+.
* Or the suffix can be a single letter without a number.
*
* Examples:
* 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
*
* @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.
*
* - The ints are compared arithmetically. In case of ties,
* the version with the most numbers wins.
* - If both versions' suffixes have a number, and the same
* characters appear before that number, then the suffix number
* is compared arithmetically.
* - Then the entire suffix is compared alphabetically.
* - 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;
}
}