blob: f81a113a4e0d39b6c81273656ab0218e707b2288 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.util.text;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Leonid Shalupov
*
* This versions comparator is much smarter than StringUtil.compareVersionNumbers
* E.g: is used for TeamCity plugins and Ruby gems versions
*/
public class VersionComparatorUtil {
private static final Pattern WORDS_SPLITTER = Pattern.compile("\\d+|[^\\d]+");
private static final VersionTokenType[] VALUES = VersionTokenType.values();
public static final Comparator<String> COMPARATOR = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return VersionComparatorUtil.compare(s1, s2);
}
};
private VersionComparatorUtil() {
}
public static String max(final String v1, final String v2) {
return compare(v1, v2) > 0 ? v1 : v2;
}
public static String min(final String v1, final String v2) {
return compare(v1, v2) < 0 ? v1 : v2;
}
public enum VersionTokenType {
SNAP(10), SNAPSHOT(10),
M(20),
EAP(25), PRE(25), PREVIEW(25),
ALPHA(30), A(30),
BETA(40), BETTA(40), B(40),
RC(50),
_WS(60),
SP(70),
REL(80), RELEASE(80), R(80), FINAL(80),
_WORD(90),
_DIGITS(100),
BUNDLED(666);
private final int myPriority;
VersionTokenType(final int priority) {
myPriority = priority;
}
@NotNull
public static VersionTokenType lookup(String str) {
if (str == null) {
return _WS;
}
str = str.trim();
if (str.isEmpty()) {
return _WS;
}
for (VersionTokenType token : VALUES) {
final String name = token.name();
if (name.charAt(0) != '_' && name.equalsIgnoreCase(str)) {
return token;
}
}
if (str.matches("0+")) {
return _WS;
}
if (str.matches("\\d+")) {
return _DIGITS;
}
return _WORD;
}
public int getPriority() {
return myPriority;
}
}
static List<String> splitVersionString(final String ver) {
StringTokenizer st = new StringTokenizer(ver.trim(), "()._-;:/, +~");
List<String> result = new ArrayList<String>();
while (st.hasMoreTokens()) {
final Matcher matcher = WORDS_SPLITTER.matcher(st.nextToken());
while (matcher.find()) {
result.add(matcher.group());
}
}
return result;
}
/**
* Compare two version strings. See TeamCity documentation on requirements comparison
* for formal description.
*
* Examples: 1.0rc1 < 1.0release, 1.0 < 1.0.1, 1.1 > 1.02
* @return 0 if ver1 equals ver2, positive value if ver1 > ver2, negative value if ver1 < ver2
*/
public static int compare(String ver1, String ver2) {
// todo duplicates com.intellij.openapi.util.text.StringUtil.compareVersionNumbers()
// todo please refactor next time you make changes here
if (ver1 == null) {
return (ver2 == null) ? 0 : -1;
} else if (ver2 == null) {
return 1;
}
ver1 = ver1.toLowerCase();
ver2 = ver2.toLowerCase();
final List<String> s1 = splitVersionString(ver1);
final List<String> s2 = splitVersionString(ver2);
padWithNulls(s1, s2);
int res = 0;
for (int i = 0; i < s1.size(); i++) {
final String e1 = s1.get(i);
final String e2 = s2.get(i);
final VersionTokenType t1 = VersionTokenType.lookup(e1);
final VersionTokenType t2 = VersionTokenType.lookup(e2);
if (!t1.equals(t2)) {
res = comparePriorities(t1, t2);
} else if (t1 == VersionTokenType._WORD) {
res = e1.compareTo(e2);
} else if (t1 == VersionTokenType._DIGITS) {
res = compareNumbers(e1, e2);
}
if (res != 0) {
return res;
}
}
return 0;
}
private static int comparePriorities(VersionTokenType t1, VersionTokenType t2) {
return Integer.signum(t1.getPriority() - t2.getPriority());
}
private static int compareNumbers(String n1, String n2) {
// trim leading zeros
while(!n1.isEmpty() && !n2.isEmpty() && n1.charAt(0) == '0' && n2.charAt(0) == '0') {
n1 = n1.substring(1);
n2 = n2.substring(1);
}
// starts with zero => less
if (!n1.isEmpty() && n1.charAt(0) == '0') {
return -1;
} else if (!n2.isEmpty() && n2.charAt(0) == '0') {
return 1;
}
// compare as numbers
final int n1len = n1.length();
final int n2len = n2.length();
if (n1len > n2len) {
n2 = StringUtil.repeatSymbol('0', n1len - n2len) + n2;
} else if (n2len > n1len) {
n1 = StringUtil.repeatSymbol('0', n2len - n1len) + n1;
}
return n1.compareTo(n2);
}
private static void padWithNulls(final Collection<String> s1, final Collection<String> s2) {
if (s1.size() != s2.size()) {
while (s1.size() < s2.size()) {
s1.add(null);
}
while (s1.size() > s2.size()) {
s2.add(null);
}
}
}
}