package org.ovirt.engine.core.common.utils;
import java.util.List;
import org.ovirt.engine.core.compat.RpmVersion;
import org.ovirt.engine.core.compat.Version;
public class RpmVersionUtils {
private static final String INVALID_RPM_NAME_FORMAT = "RPM name should be in format of Prefix-Version-Release";
/**
* Compares two Rpm version parts. The format of Rpm is - Prefix-Version-Release. Version part is either Version or
* Release.
*
* @param part1
* part from first RPM
* @param part2
* part from second RPM
* @return
* 0 - if two parts are equals
* 1 - if the 1st argument is bigger than the 2nd
* -1 - if the 1st argument is smaller than the 2nd
*/
public static int compareRpmParts(String part1, String part2) {
// The method takes the two strings, and for each one of them -
// Calculates segments of numeric or letters (for each segment - will try
// to get its maximum possible length)
// Then it will take two segments from both strings that are located in the same
// position and compare them -
// if both segments are equal - continue to next segment
// if both segments are numeric - it will use Long comparison
// if both segments are letters - it will use String comparison
// in other cases - the string that has the numeric component wins
StringBuilder[] comps1 = fillCompsArray(part1);
StringBuilder[] comps2 = fillCompsArray(part2);
int counter = 0;
while (comps1[counter] != null && comps2[counter] != null
&& comps1[counter].toString().equals(comps2[counter].toString())) {
counter++;
}
if (comps1[counter] == null && comps2[counter] == null) {
// This means that the number of segments is equal, and that all segments in relative places are equal
return 0;
}
// part1 has more parts
if (comps1[counter] != null && comps2[counter] == null) {
return 1;
}
// part2 has more parts
if (comps2[counter] != null && comps1[counter] == null) {
return -1;
}
Long longVal1 = parseLong(comps1[counter].toString());
Long longVal2 = parseLong(comps2[counter].toString());
// Both parts are not numeric, do lexicographical comparison
if (longVal1 == null && longVal2 == null) {
return comps1[counter].toString().compareTo(comps2[counter].toString());
}
// Both parts are numeric, do numeric comparison
if (longVal1 != null && longVal2 != null) {
return longVal1.compareTo(longVal2);
}
// 2nd part is numeric - it should "win"
if (longVal1 == null) {
return -1;
}
// 1st part is numeric - it should "win"
return 1;
}
protected static Long parseLong(String str) {
try {
return Long.parseLong(str);
} catch (NumberFormatException ex) {
return null;
}
}
/**
* This method splits Rpm version (or release component) into segments. Each segment should contain only letters or
* numbers. Characters which are not alpha numeric should be treated as delimiters
*/
protected static StringBuilder[] fillCompsArray(String part) {
if (part == null) {
return null;
}
StringBuilder[] comps = new StringBuilder[part.length()];
char[] chars = part.toCharArray();
int arrayIndex = 0;
int index = 0;
int state = 0; //0 - start , 1 - alphabetic, 2 - numeric - 3 other
StringBuilder current = new StringBuilder();
// The maximum number of segments is the number of characters in the string
while (index < part.length()) {
// The current character is letter
if (isLetter(chars[index])) {
if (state == 2) {
// in case the current state is "numeric"
// this marks the end of segment, so
// put the segment in the array and
// create new buffer for the new segment
comps[arrayIndex++] = current;
current = new StringBuilder();
}
// change state to "alphabetic"
state = 1;
current.append(chars[index]);
} else if (Character.isDigit(chars[index])) {
// The current character is numeric
if (state == 1) {
// in case the current state is "alphabetic"
// this marks the end of segment, so
// put the segment in the array and
// create new buffer for the new segment
comps[arrayIndex++] = current;
current = new StringBuilder();
}
// change state to "numeric"
state = 2;
current.append(chars[index]);
} else {
// The character is not a number nor a letter
// This means a segment has ended
// The segment should be added to the array
// and a new buffer should be created
if (state == 2 || state == 1 || state == 3) {
comps[arrayIndex++] = current;
current = new StringBuilder();
}
state = 3;
}
index++;
}
if (current.length() > 0) {
comps[arrayIndex] = current;
}
return comps;
}
private static boolean isLetter(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
/**
* Splits RPM into parts of Prefix, Version and Release
*
* @return array of strings filled with prefix,version,release strings (in this order)
*/
public static String[] splitRpmToParts(String rpmName) {
int lastDashIndex = rpmName.lastIndexOf('-');
if (lastDashIndex == -1) {
throw new IllegalArgumentException(INVALID_RPM_NAME_FORMAT);
}
char[] chars = rpmName.toCharArray();
int beforeLastDashIndex = lastDashIndex - 1;
while (beforeLastDashIndex >= 0) {
if (chars[beforeLastDashIndex] == '-') {
break;
}
beforeLastDashIndex--;
}
if (beforeLastDashIndex == -1) {
throw new IllegalArgumentException(INVALID_RPM_NAME_FORMAT);
}
String[] parts = new String[3];
parts[0] = rpmName.substring(0, beforeLastDashIndex);
parts[1] = rpmName.substring(beforeLastDashIndex + 1, lastDashIndex);
parts[2] = rpmName.substring(lastDashIndex + 1);
return parts;
}
/**
* Checks if an update is available for host OS
*
* @param isos
* an images which may upgrade the given host
* @param hostOs
* the examined host OS
* @return {@code true} if an update is available, else {@code false}
*/
public static boolean isUpdateAvailable(List<RpmVersion> isos, String hostOs) {
String[] hostOsParts = hostOs.split("-");
for (int i = 0; i < hostOsParts.length; i++) {
hostOsParts[i] = hostOsParts[i].trim();
}
// hostOs holds the following components:
// hostOs[0] holds prefix
// hostOs[1] holds version
// hostOs[2] holds release
final int VERSION_FIELDS_NUMBER = 4;
// Fix hostOs[1] to be format of major.minor.build.revision
// Add ".0" for missing parts
String[] hostOsVersionParts = hostOsParts[1].split("\\.");
for (int i = 0; i < VERSION_FIELDS_NUMBER - hostOsVersionParts.length; i++) {
hostOsParts[1] = hostOsParts[1].trim() + ".0";
}
Version hostVersion = new Version(hostOsParts[1].trim());
String releaseHost = hostOsParts[2].trim();
for (RpmVersion iso : isos) {
// Major check
if (hostVersion.getMajor() == iso.getMajor()) {
// Minor and Buildiso.getRpmName()
if (iso.getMinor() > hostVersion.getMinor() || iso.getBuild() > hostVersion.getBuild()) {
return true;
}
String rpmFromIso = iso.getRpmName();
// Removes the ".iso" file extension , and get the release part from it
int isoIndex = rpmFromIso.indexOf(".iso");
if (isoIndex != -1) {
rpmFromIso = iso.getRpmName().substring(0, isoIndex);
}
if (RpmVersionUtils.compareRpmParts(RpmVersionUtils.splitRpmToParts(rpmFromIso)[2], releaseHost) > 0) {
return true;
}
}
}
return false;
}
}