Comparing version number strings (major, minor, revision, beta)

Viewed 21908

I have an application that communicates with the firmware of a device. As there are changes to the firmware, it is versioned with the format {major}.{minor}.{revision}[beta[{beta}]]. To give you a few examples, the current version is 0.4.7beta which will be followed by 0.4.7beta2 and occasionally 0.4.7, followed by 0.4.8beta. The versioning format of the firmware is unfortunately not under my control, so I can not change it.

I need a way of comparing firmwares with each other. Basically, I need a function

boolean isFirmwareNewer(String testFW, String baseFW);

What I did so far was to convert this format to a simple int. So 0.4.7beta2 will become 00040702 (2 digits for each level). The problem is, that

  1. My code is difficult to read (>40 lines and 3 methods)
  2. I am sure, there is a elegant solution to this (maybe using regex?)
  3. I want to have a wildcard 0.0.0 which is newer by definition
  4. This handles the beta versions incorrect (0.4.7beta2 is not newer than 0.4.7). That is easy to account for (if (testFW.contains("beta")) testFWValue -= 100;, but it's not really elegant as well.

How do you guys do this normally (or how would you do it)?

If you want, I can attach the code I am currently working with, but as I said, it's >40 lines of code and not really readable (which is the reason why I am looking for a better way to do this).

3 Answers

For my project I have used this approach by following Semantic Versioning 2.0.0:

private static String[] formatVersionString(String[] strArr){
    //remove trailing 0s
    List<String> list = new ArrayList<>();
    boolean foundChar = false;
    for(int i=strArr.length-1;i>=0;i--){
        String curChar = strArr[i];
        if(curChar.equals("0") && !foundChar){
            continue;
        } else{
            list.add(strArr[i]);
            foundChar = true;
        }

    }
    Collections.reverse(list);
    return list.toArray(new String[list.size()]);
}

private static String getPreReleaseBuildStr(String buildStr){
    //removing build metadata
    if(buildStr == null){
        return null;
    }
    String [] a = buildStr.split("\\+");
    if(a.length>0){
        return a[0];
    } else{
        return null;
    }
}

private static int compareVersionString(String str1,String str2){
    int ret = 0;
    String[] verStr1 = formatVersionString(str1.split("\\."));
    String[] verStr2 = formatVersionString(str2.split("\\."));

    int i = 0;
    // set index to first non-equal ordinal or length of shortest version string
    while (i < verStr1.length && i < verStr2.length && verStr1[i].equals(verStr2[i])) {
        i++;
    }

    // compare first non-equal ordinal number
    if (i < verStr1.length && i < verStr2.length) {
        int diff = 0;
        try{
            if(verStr1[i] == null || verStr1[i].trim().length() == 0) {
                verStr1[i] = "0";
            }
            if(verStr2[i] == null || verStr2[i].trim().length() == 0) {
                verStr2[i] = "0";
            }
            diff = Integer.valueOf(verStr1[i]).compareTo(Integer.valueOf(verStr2[i]));
        }catch(NumberFormatException e){
            diff = verStr1[i].compareTo(verStr2[i]);
        } finally{
            ret = Integer.signum(diff);
        }
    } else{
        // the strings are equal or one string is a substring of the other
        // e.g. "1.2.3" = "1.2.3" or "1.2.3" < "1.2.3.4"

        ret = Integer.signum(verStr1.length - verStr2.length);
    }

    return ret;
}

/**
 * Compares two version strings.
 * follow this link for more info http://semver.org/
 *
 * Use this instead of String.compareTo() for a non-lexicographical
 * comparison that works for version strings. e.g. "1.10".compareTo("1.6").
 *
 * Ex:--
 * //1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0 < 2.0.0.6
 *
 * @param str1 a string of ordinal numbers separated by decimal points.
 * @param str2 a string of ordinal numbers separated by decimal points.
 * @return The result is a negative integer if str1 is _numerically_ less than str2.
 *         The result is a positive integer if str1 is _numerically_ greater than str2.
 *         The result is zero if the strings are _numerically_ equal.
 */

public static int versionCompare(String str1, String str2) {

    int ret = 0;
    String[] val1 = str1.split("-");
    String[] val2 = str2.split("-");

    String preReleaseVer1 = null, preReleaseVer2 = null;
    if(val1.length>1){
        preReleaseVer1 = getPreReleaseBuildStr(val1[1]);
    }
    if(val2.length>1){
        preReleaseVer2 = getPreReleaseBuildStr(val2[1]);
    }

    ret = compareVersionString(val1[0],val2[0]);

    if(ret == 0){
        //if both version are equal then compare with pre_release String
        if(preReleaseVer1 == null && preReleaseVer2 == null){
              ret = 0;
        } else if(preReleaseVer1 == null && preReleaseVer2!=null){
            //1.0.0 > 1.0.0-beta
              ret = 1;
        } else if(preReleaseVer1 != null && preReleaseVer2==null){
            //1.0.0-beta < 1.0.0
            ret = -1;
        } else{
            //both hasve pre release string
            ret = compareVersionString(preReleaseVer1,preReleaseVer2);
        }
    }

    return ret;
}

Use versionCompare(String str1, String str2) method for any comparison

Code Example:---

public class Main
{
    public static void main (String[] args)
    {
        //1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0

        Test(new String[]{"1.0.0-alpha","1.0.0-alpha.1","1.0.0-alpha.beta","1.0.0-beta",
        "1.0.0-beta.2","1.0.0-beta.11","1.0.0-rc.1","1.0.0","2.0.0.1"});  
    }
    private static void Test(String[] versions) {
        for (int i = 0; i < versions.length; i++) {
            for (int j = 0; j < versions.length; j++) {
            Test(versions[i], versions[j]);
            }
        }
    }

    private static void Test(String v1, String v2) {
        int result = versionCompare(v1,v2);
        String op = "==";
        if (result < 0) op = "<";
        if (result > 0) op = ">";
        System.out.printf("%s %s %s\n", v1, op, v2);
    }
}

The Output of the above example is given below:---

1.0.0-alpha == 1.0.0-alpha
1.0.0-alpha < 1.0.0-alpha.1
1.0.0-alpha < 1.0.0-alpha.beta
1.0.0-alpha < 1.0.0-beta
1.0.0-alpha < 1.0.0-beta.2
1.0.0-alpha < 1.0.0-beta.11
1.0.0-alpha < 1.0.0-rc.1
1.0.0-alpha < 1.0.0
1.0.0-alpha < 2.0.0.1
1.0.0-alpha.1 > 1.0.0-alpha
1.0.0-alpha.1 == 1.0.0-alpha.1
1.0.0-alpha.1 < 1.0.0-alpha.beta
1.0.0-alpha.1 < 1.0.0-beta
1.0.0-alpha.1 < 1.0.0-beta.2
1.0.0-alpha.1 < 1.0.0-beta.11
1.0.0-alpha.1 < 1.0.0-rc.1
1.0.0-alpha.1 < 1.0.0
1.0.0-alpha.1 < 2.0.0.1
1.0.0-alpha.beta > 1.0.0-alpha
1.0.0-alpha.beta > 1.0.0-alpha.1
1.0.0-alpha.beta == 1.0.0-alpha.beta
1.0.0-alpha.beta < 1.0.0-beta
1.0.0-alpha.beta < 1.0.0-beta.2
1.0.0-alpha.beta < 1.0.0-beta.11
1.0.0-alpha.beta < 1.0.0-rc.1
1.0.0-alpha.beta < 1.0.0
1.0.0-alpha.beta < 2.0.0.1
1.0.0-beta > 1.0.0-alpha
1.0.0-beta > 1.0.0-alpha.1
1.0.0-beta > 1.0.0-alpha.beta
1.0.0-beta == 1.0.0-beta
1.0.0-beta < 1.0.0-beta.2
1.0.0-beta < 1.0.0-beta.11
1.0.0-beta < 1.0.0-rc.1
1.0.0-beta < 1.0.0
1.0.0-beta < 2.0.0.1
1.0.0-beta.2 > 1.0.0-alpha
1.0.0-beta.2 > 1.0.0-alpha.1
1.0.0-beta.2 > 1.0.0-alpha.beta
1.0.0-beta.2 > 1.0.0-beta
1.0.0-beta.2 == 1.0.0-beta.2
1.0.0-beta.2 < 1.0.0-beta.11
1.0.0-beta.2 < 1.0.0-rc.1
1.0.0-beta.2 < 1.0.0
1.0.0-beta.2 < 2.0.0.1
1.0.0-beta.11 > 1.0.0-alpha
1.0.0-beta.11 > 1.0.0-alpha.1
1.0.0-beta.11 > 1.0.0-alpha.beta
1.0.0-beta.11 > 1.0.0-beta
1.0.0-beta.11 > 1.0.0-beta.2
1.0.0-beta.11 == 1.0.0-beta.11
1.0.0-beta.11 < 1.0.0-rc.1
1.0.0-beta.11 < 1.0.0
1.0.0-beta.11 < 2.0.0.1
1.0.0-rc.1 > 1.0.0-alpha
1.0.0-rc.1 > 1.0.0-alpha.1
1.0.0-rc.1 > 1.0.0-alpha.beta
1.0.0-rc.1 > 1.0.0-beta
1.0.0-rc.1 > 1.0.0-beta.2
1.0.0-rc.1 > 1.0.0-beta.11
1.0.0-rc.1 == 1.0.0-rc.1
1.0.0-rc.1 < 1.0.0
1.0.0-rc.1 < 2.0.0.1
1.0.0 > 1.0.0-alpha
1.0.0 > 1.0.0-alpha.1
1.0.0 > 1.0.0-alpha.beta
1.0.0 > 1.0.0-beta
1.0.0 > 1.0.0-beta.2
1.0.0 > 1.0.0-beta.11
1.0.0 > 1.0.0-rc.1
1.0.0 == 1.0.0
1.0.0 < 2.0.0.1
2.0.0.1 > 1.0.0-alpha
2.0.0.1 > 1.0.0-alpha.1
2.0.0.1 > 1.0.0-alpha.beta
2.0.0.1 > 1.0.0-beta
2.0.0.1 > 1.0.0-beta.2
2.0.0.1 > 1.0.0-beta.11
2.0.0.1 > 1.0.0-rc.1
2.0.0.1 > 1.0.0
2.0.0.1 == 2.0.0.1
3.5.6-beta == 3.5.6-beta
3.5.6-beta == 3.5.6-beta.0
3.5.6-beta.0 == 3.5.6-beta
3.5.6-beta.0 == 3.5.6-beta.0
Related