All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.redhat.ceylon.cmr.api.MavenVersionComparator Maven / Gradle / Ivy

There is a newer version: 1.3.3
Show newest version
package com.redhat.ceylon.cmr.api;

import java.util.Comparator;

/**
 * Compares two version strings according to the Maven rules:
 * 
 * https://cwiki.apache.org/confluence/display/MAVENOLD/Versioning
 *
 * @author Stéphane Épardaud 
 */
public class MavenVersionComparator implements Comparator{

	public static final MavenVersionComparator INSTANCE = new MavenVersionComparator();

    @Override
	public int compare(String a, String b) {
		return compareVersions(a, b);
	}

	public static int compareVersions(String versionAString, String versionBString){
        char[] versionA = versionAString.toCharArray();
        char[] versionB = versionBString.toCharArray();
        int aStart = 0, aEnd = 0;
        int bStart = 0, bEnd = 0;
        int aLevel = 0, bLevel = 0;
        while(true){
            // if we've exhausted both, we stop
            if(aEnd == versionA.length && bEnd == versionB.length)
                return 0;

            boolean aString = false;
            boolean bString = false;
            boolean aNumber = false;
            boolean bNumber = false;
            long a = 0, b = 0;

            // collect all chars until they change type
            while(aEnd < versionA.length && !isSeparator(versionA[aEnd])){
                char c = versionA[aEnd];
                if(!aString && !aNumber){
                    // see what we're looking for
                    if(Character.isDigit(c)){
                        aNumber = true;
                        a = (versionA[aEnd] - '0');
                    }else
                        aString = true;
                }else if(aNumber){ 
                    if(!Character.isDigit(c))
                        break;
                    a = a * 10 + (versionA[aEnd] - '0');
                }else if(aString && Character.isDigit(c))
                    break;
                // eat it
                aEnd++;
            }
            while(bEnd < versionB.length && !isSeparator(versionB[bEnd])){
                char c = versionB[bEnd];
                if(!bString && !bNumber){
                    // see what we're looking for
                    if(Character.isDigit(c)){
                        bNumber = true;
                        b = (versionB[bEnd] - '0');
                    }else
                        bString = true;
                }else if(bNumber){ 
                    if(!Character.isDigit(c))
                        break;
                    b = b * 10 + (versionB[bEnd] - '0');
                }else if(bString && Character.isDigit(c))
                    break;
                // eat it
                bEnd++;
            }
            int compare = compare(versionA, aStart, aEnd, aNumber, aString, a, aLevel,
                                  versionB, bStart, bEnd, bNumber, bString, b, bLevel);
            if(compare != 0)
                return compare;
            if(aEnd < versionA.length && versionA[aEnd] == '-')
                aLevel++;
            if(bEnd < versionB.length && versionB[bEnd] == '-')
                bLevel++;
            // if not, eat any optional separator (we can be separated by just string/int) and loop
            if(aEnd < versionA.length && isSeparator(versionA[aEnd]))
                aEnd++;
            if(bEnd < versionB.length && isSeparator(versionB[bEnd]))
                bEnd++;
            aStart = aEnd;
            bStart = bEnd;
        }
    }
    
    private static boolean isSeparator(char c) {
        return c == '.' || c == '-';
    }

    private static int compare(char[] a, int aStart, int aEnd, boolean aNumber, boolean aString, long aValue, int aLevel, 
                               char[] b, int bStart, int bEnd, boolean bNumber, boolean bString, long bValue, int bLevel) {
        if(aLevel > bLevel){
            // A is in a list, and B is not
            if(bNumber)
                return -1; // number is newer
            if(bString)
                return 1; // list is newer
            // b is null, compare our element against it
        }else if(aLevel < bLevel){
            // B is in a list, and A is not
            if(aNumber)
                return 1; // number is newer
            if(aString)
                return -1; // list is newer
            // a is null, compare our element against it
        }

        if(aNumber){
            if(bNumber)
                return Long.compare(aValue, bValue);
            if(bString)
                return 1; // number is newer
            // must be empty
            if(aValue == 0)
                return 0; // 0 == ""
            return 1; // number is newer
        }
        if(aString){
            if(bNumber)
                return -1; // number is newer
            // either we have bString, or not and it's empty, which compareStrings handles
            return compareStrings(a, aStart, aEnd, b, bStart, bEnd);
        }
        // a is empty
        if(bNumber){
            if(bValue == 0)
                return 0; // 0 == ""
            return -1; // number is newer
        }
        if(bString){
            // we have bString and compareStrings handles a's empty
            return compareStrings(a, aStart, aEnd, b, bStart, bEnd);
        }
        // both empty
        return 0;
    }

    private static int compareStrings(char[] a, int aStart, int aEnd, char[] b, int bStart, int bEnd) {
        int specialWeightA = getSpecialWeight(a, aStart, aEnd);
        int specialWeightB = getSpecialWeight(b, bStart, bEnd);
        if(specialWeightA >= 0){
            if(specialWeightB >= 0)
                return Integer.compare(specialWeightA, specialWeightB);
            // special weight is newer
            return -1;
        }
        if(specialWeightB >= 0){
            // special weight is newer
            return 1;
        }
        // no special weight, normal comparison
        for (; aStart < aEnd && bStart < bEnd; aStart++, bStart++) {
            char aChar = a[aStart];
            char bChar = b[bStart];
            int ret = Character.compare(aChar, bChar);
            if(ret != 0)
                return ret;
            // equal, try the next char
        }
        // shortest one wins
        if(aStart == aEnd && bStart == bEnd)
            return 0;
        if(aStart == aEnd)
            return -1;
        return 1;
    }

    private static int getSpecialWeight(char[] chars, int start, int end) {
        if(start == end)
            return 5;
        if(equalsIgnoreCase("alpha", chars, start, end)
                || equalsIgnoreCase("a", chars, start, end))
            return 0;
        if(equalsIgnoreCase("beta", chars, start, end)
                || equalsIgnoreCase("b", chars, start, end))
            return 1;
        if(equalsIgnoreCase("milestone", chars, start, end)
                || equalsIgnoreCase("m", chars, start, end))
            return 2;
        if(equalsIgnoreCase("cr", chars, start, end)
                || equalsIgnoreCase("rc", chars, start, end))
            return 3;
        if(equalsIgnoreCase("snapshot", chars, start, end))
            return 4;
        if(equalsIgnoreCase("ga", chars, start, end)
                || equalsIgnoreCase("final", chars, start, end))
            return 5;
        if(equalsIgnoreCase("sp", chars, start, end))
            return 6;
        return -1;
    }

    private static boolean equalsIgnoreCase(String string, char[] chars, int start, int end) {
        // same length?
        if((end - start) != string.length())
            return false;
        // since they have the same length, we have enough i, no need to check
        for (int i=0; start < end ; start++, i++) {
            char aChar = chars[start];
            char bChar = string.charAt(i);
            int ret = Character.compare(Character.toLowerCase(aChar), Character.toLowerCase(bChar));
            if(ret != 0)
                return false;
            // equal, try the next char
        }
        // equal
        return true;
    }

    /**
     * Returns versionA and versionB ordered
     */
    public static String[] orderVersions(String versionA, String versionB) {
        if(MavenVersionComparator.compareVersions(versionA, versionB) > 0){
            String permute = versionA;
            versionA = versionB;
            versionB = permute;
        }
        return new String[]{ versionA, versionB };
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy