/*******************************************************************************
* Copyright (c) 2015 ARM Ltd. and others
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* ARM Ltd and ARM Germany GmbH - Initial API and implementation
*******************************************************************************/
package com.arm.cmsis.pack.utils;
/**
* Class to compare version strings according to Semantic Versioning 2.0
* <p/>
* The class can be used:
* <ul>
* <li> as comparator to sort collections (in descending order by default)
* <li> to compare version strings directly using <code>versionCompare()</code> static functions
* </ul>
* @see <a href="http://semver.org">http://semver.org</a>
*/
public class VersionComparator extends AlnumComparator{
/**
* Constructs default case insensitive comparator with descending sort order
*
*/
public VersionComparator() {
}
/**
* Constructs case insensitive comparator
* @param descending - sorting order: true - descending, false - acceding
*/
public VersionComparator(boolean descending) {
super(descending);
}
/**
* Constructs comparator
* @param descending sorting order: true - descending, false - acceding
* @param caseSensitive comparison
*/
public VersionComparator(boolean descending, boolean caseSensitive) {
super(descending, caseSensitive);
}
@Override
protected int compare(String ver1, String ver2, boolean cs) {
return versionCompare(ver1, ver2, cs);
}
/**
* Semantically compares two version strings respecting case
* @param ver1 first version string to compare
* @param ver2 second version string to compare
* @param cs case sensitive flag for non-numeric values
* @return
* <dd><b> 4</b> major of ver1 greater than major ver2</dd>
* <dd><b> 3</b> minor of ver1 greater than minor ver2</dd>
* <dd><b> 2</b> patch of ver1 greater than patch ver2</dd>
* <dd><b> 1</b> release of ver1 greater than release ver2</dd>
* <dd><b> 0</b> ver1 equals ver2</dd>
* <dd><b>-1</b> release of ver1 less than release of ver2</dd>
* <dd><b>-2</b> patch of ver1 less than patch of ver2</dd>
* <dd><b>-3</b> minor of ver1 less than minor of ver2</dd>
* <dd><b>-4</b> major of ver1 less than major of ver2</dd>
*/
public static int versionCompare(String ver1, String ver2, boolean cs){
// allow comparison of null and empty strings
if (ver1 == null) {
if (ver2 == null)
return 0;
return -4;
} else if (ver2 == null) {
return 4;
}
Version v1 = new Version(ver1);
Version v2 = new Version(ver2);
return v1.compareTo(v2, cs);
}
/**
* Semantically compares two version strings respecting case
* @param ver1 - first version string to compare
* @param ver2 - second version string to compare
* @return comparison result - see versionCompare(String, String, boolean)
* @see #versionCompare(String, String, boolean)
*/
public static int versionCompare(final String str1, final String str2) {
return versionCompare(str1, str2, true);
}
/**
* Semantically compares two version strings respecting case
* @param ver1 - first version string to compare
* @param ver2 - second version string to compare
* @return comparison result - see versionCompare(String, String, boolean)
* @see #versionCompare(String, String, boolean)
*/
public static int versionCompareNoCase(final String str1, final String str2) {
return versionCompare(str1, str2, true);
}
/**
* Check if supplied version matches supplied version range
* @param version version to check
* @param versionRange string with version range in the form <code>"min[:max]"</code>
* @return true if version is larger or equal to maximum and smaller or equal optional maximum
*/
static public boolean matchVersionRange(final String version, final String versionRange){
if(version == null || versionRange == null)
return true;
if(version.isEmpty() || versionRange.isEmpty())
return true;
String verMin = null;
String verMax = null;
int i = versionRange.indexOf(':');
if (i >= 0) {
verMin = versionRange.substring(0, i);
verMax = versionRange.substring(i + 1);
} else{
verMin = versionRange;
}
if(verMin != null && !verMin.isEmpty()){
int res = versionCompare(version, verMin);
if( res < 0)
return false;
if(verMin.equals(verMax))
return res == 0;
}
if(verMax != null && !verMax.isEmpty()){
if(versionCompare(version, verMax) > 0)
return false;
}
return true;
}
/**
* Internal helper class
*/
static private class Version implements Comparable<Version> {
private static final String ZERO_STRING = "0"; //$NON-NLS-1$
private String[] segments = null; // first three version segments : MAJOR.MINOR.PATCH
private String release = null; // remainder (after '-');
private int fLevel;
Version(String ver){
this(ver, 0);
}
Version(String ver, int level){
fLevel = level;
if(ver == null)
throw new IllegalArgumentException("Version can not be null"); //$NON-NLS-1$
// 1. drop build metadata
int pos = ver.indexOf('+');
if(pos >= 0)
ver = ver.substring(0, pos);
// 2. extract release
pos = ver.indexOf('-');
if(pos >=0 ) {
release = ver.substring(pos + 1);
ver = ver.substring(0, pos);
} else if( fLevel == 0 && !ver.isEmpty()) {
// check for special ST case without dash like 1.2.3b < 1.2.3
int lastIndex = ver.length() - 1;
for(pos = lastIndex ; pos >=0 ; pos--) {
char ch = ver.charAt(pos);
if(ch == '.')
break;
if(!Character.isDigit(ch))
continue;
if(pos < lastIndex) {
release = ver.substring(pos);
ver = ver.substring(0, pos);
}
break;
}
}
// 3. split segments
if(ver != null) {
segments = ver.split("\\."); //$NON-NLS-1$
}
}
public String getRelease() {
return release;
}
public int getSegmentCount() {
return segments != null ? segments.length : 0;
}
public String getSegment(int index) {
if(index >= 0 && index < getSegmentCount())
return segments[index];
return ZERO_STRING;
}
@Override
public int compareTo(Version that) {
return compareTo(that, true);
}
public int compareTo(Version that, boolean cs) {
int result = 4;
if(that == null)
return result;
int length = Math.max(this.getSegmentCount(), that.getSegmentCount());
for(int i = 0 ; i < length; i++) {
String thisSegment = this.getSegment(i);
String thatSegment = that.getSegment(i);
int res = alnumCompare(thisSegment, thatSegment, cs);
if(res != 0)
return res > 0 ? result : -result;
if(result > 1)
result--;
}
String thisRelease = this.getRelease();
String thatRelease = that.getRelease();
if(thisRelease == null && thatRelease == null)
return 0;
else if(thisRelease == null)
return 1;
else if(thatRelease == null)
return -1;
// compare releases
Version v1 = new Version(thisRelease, fLevel + 1);
Version v2 = new Version(thatRelease, fLevel + 1);
result = v1.compareTo(v2, false); // case insensitive compare for release revision
if(result < 0){
return -1;
} else if(result > 0) {
return 1;
}
return 0;
}
}
}