package de.cinovo.cloudconductor.server.comparators;
/*
* #%L
* cloudconductor-server
* %%
* Copyright (C) 2013 - 2014 Cinovo AG
* %%
* 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.
* #L%
*/
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Copyright 2013 Cinovo AG<br>
* <br>
* Comparator for comparing version strings.
*
* Note that the comparison algorithm used here does not aim to faithfully imitate the one used by the RPM Package Manager. It serves as a
* simple implementation that should work with most reasonable version naming schemes.
*
* @author mhilbert
*/
public class VersionStringComparator implements Comparator<String> {
@Override
public int compare(String version1, String version2) {
// Split version and release.
String[] parts1 = version1.split("-", 2);
parts1 = (parts1.length == 2) && !parts1[1].isEmpty() ? parts1 : new String[] {parts1[0], "1"};
String[] parts2 = version2.split("-", 2);
parts2 = (parts2.length == 2) && !parts2[1].isEmpty() ? parts2 : new String[] {parts2[0], "1"};
// Compare version. Empty string is less than non-empty string.
if (version1.isEmpty() != version2.isEmpty()) {
return Boolean.compare(!version1.isEmpty(), !version2.isEmpty());
}
String[] v1 = parts1[0].split("\\.");
String[] v2 = parts2[0].split("\\.");
for (int i = 0; i < Math.max(v1.length, v2.length); ++i) {
String x1 = i < v1.length ? v1[i] : "0";
String x2 = i < v2.length ? v2[i] : "0";
int c = this.compareVersionParts(x1, x2);
if (c != 0) {
return c;
}
}
// Compare release.
return this.compareVersionParts(parts1[1], parts2[1]);
}
private int compareVersionParts(String input1, String input2) {
// Split parts at digit-to-non-digit boundaries, i.e. into parts that are either numeric or non-numeric strings. The individual
// parts of both inputs will be compared pairwise from beginning to end until a pair is not equal, which will decide the comparison.
// If no such pair is found the two inputs are considered equal.
String[] r1 = this.splitAtDigitBoundaries(input1);
String[] r2 = this.splitAtDigitBoundaries(input2);
for (int i = 0; i < Math.max(r1.length, r2.length); ++i) {
// Get string part for this index (default is empty string).
String s1 = i < r1.length ? r1[i] : "";
String s2 = i < r2.length ? r2[i] : "";
// Empty string is less than non-empty string.
if (s1.isEmpty() != s2.isEmpty()) {
return Boolean.compare(!s1.isEmpty(), !s2.isEmpty());
}
// Check if the string parts begin with digits (if so, they only contain digits, because we split at digit-to-non-digit
// boundaries).
boolean b1 = this.beginsWithDigit(s1);
boolean b2 = this.beginsWithDigit(s2);
if ((b1 == true) && (b2 == true)) { // both parts are numbers
// Compare length of the numbers (without leading zeros).
String ss1 = this.stripLeadingZeros(s1);
String ss2 = this.stripLeadingZeros(s2);
int c = Integer.compare(ss1.length(), ss2.length());
if (c != 0) {
return c;
}
// The numbers are of equal lengths. Perform a lexicographic comparison.
c = ss1.compareTo(ss2);
if (c != 0) {
return c;
}
// The numbers are the same without leading zeros. Let the one with more leading zeros be greater, i.e. compare lengths
// without removing leading zeros.
c = Integer.compare(s1.length(), s2.length());
if (c != 0) {
return c;
}
} else if ((b1 == false) && (b2 == false)) { // neither of the parts is a number
// Perform a lexicographic comparison.
int c = s1.compareTo(s2);
if (c != 0) {
return c;
}
} else { // one part is a number, the other is not
// Number trumps non-number.
return Boolean.compare(b1, b2);
}
}
// No pairwise difference found. The two inputs are considered equal.
return 0;
}
private String[] splitAtDigitBoundaries(String str) {
Matcher matcher = Pattern.compile("\\d+").matcher(str);
List<String> parts = new ArrayList<String>();
int pos = 0;
while (matcher.find()) {
if (matcher.start() > 0) {
parts.add(str.substring(pos, matcher.start()));
}
parts.add(matcher.group());
pos = matcher.end();
}
if (pos < str.length()) {
parts.add(str.substring(pos, str.length()));
}
return parts.toArray(new String[parts.size()]);
}
private boolean beginsWithDigit(String str) {
return str.substring(0, 1).matches("\\d");
}
private String stripLeadingZeros(String str) {
int i = 0;
while ((i < str.length()) && (str.charAt(i) == '0')) {
i++;
}
return str.substring(i, str.length());
}
}