/*
* Copyright 2012-present Facebook, Inc.
*
* 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.facebook.buck.util;
import com.google.common.collect.ImmutableList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Compares version strings such as "4.2.2", "17.0", "r10e-rc4".
*
* <p>Comparing different schemas e.g. "r10e-rc4" vs "4.2.2" is undefined.
*/
public class VersionStringComparator implements Comparator<String> {
private static final Pattern VERSION_STRING_PATTERN =
Pattern.compile("^[rR]?(\\d+)[a-zA-Z]*(\\.\\d+)*?([-_]rc\\d+)*?(?:-preview)?$");
private static final Pattern IGNORED_FIELDS_PATTERN = Pattern.compile("(?:^[rR])|(?:-preview)");
private static final Pattern DELIMITER_PATTERN = Pattern.compile("\\.|(?:[-_]rc[0-9]+)");
private static final Pattern RC_DELIMITER_PATTERN = Pattern.compile("(\\.|\\d+|[a-zA-Z])*[-_]rc");
private static final Pattern NUMBER_ALPHA_PATTERN = Pattern.compile("(\\d+)([a-zA-Z]+)");
private static final String RC_DETECTION_PATTERN = ".*[-_]rc\\d+";
public static boolean isValidVersionString(String str) {
return VERSION_STRING_PATTERN.matcher(str).matches();
}
@Override
public int compare(String a, String b) {
if (!isValidVersionString(a)) {
throw new RuntimeException("Invalid version string: " + a);
}
if (!isValidVersionString(b)) {
throw new RuntimeException("Invalid version string: " + b);
}
String cleanedA = IGNORED_FIELDS_PATTERN.matcher(a).replaceAll("");
String cleanedB = IGNORED_FIELDS_PATTERN.matcher(b).replaceAll("");
String[] partsA = DELIMITER_PATTERN.split(cleanedA);
String[] partsB = DELIMITER_PATTERN.split(cleanedB);
Iterator<Integer> valuesA = partsToValues(partsA).iterator();
Iterator<Integer> valuesB = partsToValues(partsB).iterator();
while (valuesA.hasNext()) {
if (!valuesB.hasNext()) {
return 1;
}
int comp = valuesA.next().compareTo(valuesB.next());
if (comp != 0) {
return comp;
}
}
if (valuesB.hasNext()) {
return -1;
}
boolean isRcA = cleanedA.matches(RC_DETECTION_PATTERN);
boolean isRcB = cleanedB.matches(RC_DETECTION_PATTERN);
if (isRcA && isRcB) {
int rcVersionA = Integer.parseInt(RC_DELIMITER_PATTERN.matcher(cleanedA).replaceAll(""));
int rcVersionB = Integer.parseInt(RC_DELIMITER_PATTERN.matcher(cleanedB).replaceAll(""));
if (rcVersionA == rcVersionB) {
return 0;
}
return (rcVersionA > rcVersionB) ? 1 : -1;
} else if (isRcA) {
return -1;
} else if (isRcB) {
return 1;
}
return 0;
}
private ImmutableList<Integer> partsToValues(String[] stringParts) {
ImmutableList.Builder<Integer> valuesBuilder = new ImmutableList.Builder<>();
for (String part : stringParts) {
Matcher matcher = NUMBER_ALPHA_PATTERN.matcher(part);
if (matcher.matches()) {
valuesBuilder.add(Integer.parseInt(matcher.group(1)));
String characters = matcher.group(2);
int value = 0;
for (int i = 0; i < characters.length(); i++) {
value += Math.pow(100, characters.length() - i) * characters.charAt(i);
}
valuesBuilder.add(value);
} else {
valuesBuilder.add(Integer.parseInt(part));
}
}
return valuesBuilder.build();
}
}