added VersionComparator
This commit is contained in:
@@ -1,17 +1,6 @@
|
|||||||
package net.woggioni.jwo;
|
package net.woggioni.jwo;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.TreeMap;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.BinaryOperator;
|
import java.util.function.BinaryOperator;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
@@ -132,10 +121,58 @@ public class CollectionUtils {
|
|||||||
return toUnmodifiableMap(HashMap::new, keyExtractor, valueExtractor);
|
return toUnmodifiableMap(HashMap::new, keyExtractor, valueExtractor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T, K, V> Collector<T, ?, Map<K, V>> toUnmodifiableTreeMap(
|
public static <T, K, V> Collector<T, ?, NavigableMap<K, V>> toUnmodifiableTreeMap(
|
||||||
Function<T, K> keyExtractor,
|
Function<T, K> keyExtractor,
|
||||||
Function<T, V> valueExtractor) {
|
Function<T, V> valueExtractor) {
|
||||||
return toUnmodifiableMap(TreeMap::new, keyExtractor, valueExtractor);
|
return toUnmodifiableNavigableMap(TreeMap::new, keyExtractor, valueExtractor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, K, V> Collector<T, ?, NavigableMap<K, V>> toUnmodifiableTreeMap(
|
||||||
|
Function<T, K> keyExtractor,
|
||||||
|
Function<T, V> valueExtractor,
|
||||||
|
Comparator<K> comparator) {
|
||||||
|
return toUnmodifiableNavigableMap(() -> new TreeMap<>(comparator), keyExtractor, valueExtractor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, K, V> Collector<T, ?, NavigableMap<K, V>> toTreeMap(
|
||||||
|
Function<T, K> keyExtractor,
|
||||||
|
Function<T, V> valueExtractor) {
|
||||||
|
return toNavigableMap(TreeMap::new, keyExtractor, valueExtractor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, K, V> Collector<T, ?, NavigableMap<K, V>> toTreeMap(
|
||||||
|
Function<T, K> keyExtractor,
|
||||||
|
Function<T, V> valueExtractor,
|
||||||
|
Comparator<K> comparator) {
|
||||||
|
return toNavigableMap(() -> new TreeMap<>(comparator), keyExtractor, valueExtractor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, K, V> Collector<T, ?, NavigableMap<K, V>> toNavigableMap(
|
||||||
|
Supplier<NavigableMap<K, V>> constructor,
|
||||||
|
Function<T, K> keyExtractor,
|
||||||
|
Function<T, V> valueExtractor) {
|
||||||
|
BiConsumer<NavigableMap<K, V>, T> accumulator = (map, streamElement) -> {
|
||||||
|
map.merge(keyExtractor.apply(streamElement), valueExtractor.apply(streamElement), throwingMerger());
|
||||||
|
};
|
||||||
|
return Collector.of(
|
||||||
|
constructor,
|
||||||
|
accumulator,
|
||||||
|
mapMerger(throwingMerger())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T, K, V> Collector<T, ?, Map<K, V>> toMap(
|
||||||
|
Supplier<Map<K, V>> constructor,
|
||||||
|
Function<T, K> keyExtractor,
|
||||||
|
Function<T, V> valueExtractor) {
|
||||||
|
BiConsumer<Map<K, V>, T> accumulator = (map, streamElement) -> {
|
||||||
|
map.merge(keyExtractor.apply(streamElement), valueExtractor.apply(streamElement), throwingMerger());
|
||||||
|
};
|
||||||
|
return Collector.of(
|
||||||
|
constructor,
|
||||||
|
accumulator,
|
||||||
|
mapMerger(throwingMerger())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T, K, V> Collector<T, ?, Map<K, V>> toUnmodifiableMap(
|
public static <T, K, V> Collector<T, ?, Map<K, V>> toUnmodifiableMap(
|
||||||
@@ -152,4 +189,19 @@ public class CollectionUtils {
|
|||||||
Collections::unmodifiableMap
|
Collections::unmodifiableMap
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T, K, V> Collector<T, ?, NavigableMap<K, V>> toUnmodifiableNavigableMap(
|
||||||
|
Supplier<NavigableMap<K, V>> constructor,
|
||||||
|
Function<T, K> keyExtractor,
|
||||||
|
Function<T, V> valueExtractor) {
|
||||||
|
BiConsumer<NavigableMap<K, V>, T> accumulator = (map, streamElement) -> {
|
||||||
|
map.merge(keyExtractor.apply(streamElement), valueExtractor.apply(streamElement), throwingMerger());
|
||||||
|
};
|
||||||
|
return Collector.of(
|
||||||
|
constructor,
|
||||||
|
accumulator,
|
||||||
|
mapMerger(throwingMerger()),
|
||||||
|
Collections::unmodifiableNavigableMap
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,8 +10,10 @@ import java.lang.reflect.Constructor;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
@@ -20,6 +22,7 @@ import java.util.stream.Stream;
|
|||||||
import java.util.stream.StreamSupport;
|
import java.util.stream.StreamSupport;
|
||||||
import java.util.zip.CRC32;
|
import java.util.zip.CRC32;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -434,7 +437,7 @@ public class JWO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public void writeZipEntry(
|
public static void writeZipEntry(
|
||||||
ZipOutputStream zip,
|
ZipOutputStream zip,
|
||||||
Supplier<InputStream> source,
|
Supplier<InputStream> source,
|
||||||
String destinationFileName,
|
String destinationFileName,
|
||||||
@@ -462,7 +465,7 @@ public class JWO {
|
|||||||
zip.closeEntry();
|
zip.closeEntry();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeZipEntry(
|
public static void writeZipEntry(
|
||||||
ZipOutputStream zip,
|
ZipOutputStream zip,
|
||||||
Supplier<InputStream> source,
|
Supplier<InputStream> source,
|
||||||
String destinationFileName,
|
String destinationFileName,
|
||||||
@@ -470,10 +473,71 @@ public class JWO {
|
|||||||
writeZipEntry(zip, source, destinationFileName, compressionMethod, new byte[0x10000]);
|
writeZipEntry(zip, source, destinationFileName, compressionMethod, new byte[0x10000]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeZipEntry(
|
public static void writeZipEntry(
|
||||||
ZipOutputStream zip,
|
ZipOutputStream zip,
|
||||||
Supplier<InputStream> source,
|
Supplier<InputStream> source,
|
||||||
String destinationFileName) {
|
String destinationFileName) {
|
||||||
writeZipEntry(zip, source, destinationFileName, ZipEntry.DEFLATED);
|
writeZipEntry(zip, source, destinationFileName, ZipEntry.DEFLATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static void extractZip(Path sourceArchive, Path destinationFolder) {
|
||||||
|
byte[] buffer = new byte[0x10000];
|
||||||
|
expandZip(sourceArchive, new BiConsumer<ZipInputStream, ZipEntry>() {
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
public void accept(ZipInputStream zipInputStream, ZipEntry zipEntry) {
|
||||||
|
Path newFile = destinationFolder.resolve(zipEntry.getName());
|
||||||
|
Files.createDirectories(newFile.getParent());
|
||||||
|
try(OutputStream outputStream = Files.newOutputStream(newFile)) {
|
||||||
|
while (true) {
|
||||||
|
int read = zipInputStream.read(buffer);
|
||||||
|
if (read < 0) break;
|
||||||
|
outputStream.write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static void expandZip(Path sourceArchive, BiConsumer<ZipInputStream, ZipEntry> consumer) {
|
||||||
|
try(ZipInputStream zis = new ZipInputStream(Files.newInputStream(sourceArchive))) {
|
||||||
|
ZipEntry zipEntry = zis.getNextEntry();
|
||||||
|
while (zipEntry != null) {
|
||||||
|
consumer.accept(zis, zipEntry);
|
||||||
|
zipEntry = zis.getNextEntry();
|
||||||
|
}
|
||||||
|
zis.closeEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static void installResource(String resourceName, Path destination, Class<?> cls) {
|
||||||
|
Path outputFile;
|
||||||
|
if (Files.isSymbolicLink(destination)) {
|
||||||
|
destination = destination.toRealPath();
|
||||||
|
}
|
||||||
|
if(!Files.exists(destination)) {
|
||||||
|
Files.createDirectories(destination.getParent());
|
||||||
|
outputFile = destination;
|
||||||
|
} else if(Files.isDirectory(destination)) {
|
||||||
|
outputFile = destination.resolve(resourceName.substring(1 + resourceName.lastIndexOf('/')));
|
||||||
|
} else if(Files.isRegularFile(destination)) {
|
||||||
|
outputFile = destination;
|
||||||
|
} else {
|
||||||
|
throw newThrowable(IllegalStateException.class,
|
||||||
|
"Path '%s' is neither a file nor a directory",
|
||||||
|
destination
|
||||||
|
);
|
||||||
|
}
|
||||||
|
InputStream is = cls.getResourceAsStream(resourceName);
|
||||||
|
if(is == null) is = cls.getClassLoader().getResourceAsStream(resourceName);
|
||||||
|
if(is == null) throw new FileNotFoundException(resourceName);
|
||||||
|
try {
|
||||||
|
Files.copy(is, outputFile, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
} finally {
|
||||||
|
is.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
289
src/main/java/net/woggioni/jwo/VersionComparator.java
Normal file
289
src/main/java/net/woggioni/jwo/VersionComparator.java
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
package net.woggioni.jwo;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class VersionComparator implements Comparator<String> {
|
||||||
|
|
||||||
|
private static int indexOf(char[] haystack, char needle, int start) {
|
||||||
|
int result = -1;
|
||||||
|
for (int i = start; i < haystack.length; ++i) {
|
||||||
|
if (haystack[i] == needle) {
|
||||||
|
result = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int indexOf(char[] haystack, char needle) {
|
||||||
|
return indexOf(haystack, needle, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static char[] cstring(String s) {
|
||||||
|
char[] result = new char[s.length() + 1];
|
||||||
|
for(int i=0; i<s.length(); i++) {
|
||||||
|
result[i] = s.charAt(i);
|
||||||
|
}
|
||||||
|
result[s.length()] = '\0';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean cstringEquals(char[] cstring1, int begin1, char[] cstring2, int begin2) {
|
||||||
|
int i=0;
|
||||||
|
while(i < cstring1.length + begin1 || i < cstring2.length + begin2) {
|
||||||
|
char c1 = cstring1[begin1 + i];
|
||||||
|
char c2 = cstring2[begin2 + i];
|
||||||
|
if(c1 != c2) return false;
|
||||||
|
else if(c1 == '\0') break;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int strlen(char[] cstring, int begin) {
|
||||||
|
int i = begin;
|
||||||
|
while(i < cstring.length) {
|
||||||
|
if(cstring[i] == '\0') break;
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
return i - begin;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int strlen(char[] cstring) {
|
||||||
|
return strlen(cstring, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int strcmp(char[] cstring1, int begin1, char[] cstring2, int begin2) {
|
||||||
|
int i = 0;
|
||||||
|
int lim = Math.min(strlen(cstring1, begin1), strlen(cstring2, begin2));
|
||||||
|
while(i < lim) {
|
||||||
|
char c1 = cstring1[begin1 + i];
|
||||||
|
char c2 = cstring2[begin2 + i];
|
||||||
|
if(c1 < c2) {
|
||||||
|
return -1;
|
||||||
|
} else if(c1 > c2) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
return Integer.compare(cstring1.length, cstring2.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split EVR into epoch, version, and release components.
|
||||||
|
* @param text [epoch:]version[-release] string
|
||||||
|
* @param evr array that will contain starting indexes of epoch, version, and release
|
||||||
|
*/
|
||||||
|
private static void parseEVR(char[] text, int[] evr) {
|
||||||
|
int epoch;
|
||||||
|
int version;
|
||||||
|
int release;
|
||||||
|
|
||||||
|
int s = 0;
|
||||||
|
/* s points to epoch terminator */
|
||||||
|
while (s < text.length && Character.isDigit(text[s])) s++;
|
||||||
|
/* se points to version terminator */
|
||||||
|
int se = indexOf(text, '-', s);
|
||||||
|
|
||||||
|
if(text[s] == ':') {
|
||||||
|
epoch = 0;
|
||||||
|
text[s++] = '\0';
|
||||||
|
version = s;
|
||||||
|
if(text[epoch] == '\0') {
|
||||||
|
epoch = -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* different from RPM- always assume 0 epoch */
|
||||||
|
epoch = -1;
|
||||||
|
version = 0;
|
||||||
|
}
|
||||||
|
if(se != -1) {
|
||||||
|
text[se++] = '\0';
|
||||||
|
release = se;
|
||||||
|
} else {
|
||||||
|
release = -1;
|
||||||
|
}
|
||||||
|
evr[0] = epoch;
|
||||||
|
evr[1] = version;
|
||||||
|
evr[2] = release;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static int rpmvercmp(char[] chars1, int start1, char[] chars2, int start2) {
|
||||||
|
// easy comparison to see if versions are identical
|
||||||
|
if(strcmp(chars1, start1, chars2, start2) == 0) return 0;
|
||||||
|
char[] str1 = Arrays.copyOfRange(chars1, start1, start1 + strlen(chars1, start1) + 1);
|
||||||
|
char[] str2 = Arrays.copyOfRange(chars2, start2, start2 + strlen(chars2, start2) + 1);
|
||||||
|
int one = 0, two = 0;
|
||||||
|
int ptr1 = 0, ptr2 = 0;
|
||||||
|
boolean isNum;
|
||||||
|
char oldch1, oldch2;
|
||||||
|
|
||||||
|
// loop through each version segment of str1 and str2 and compare them
|
||||||
|
while(str1[one] != '\0' && str2[two] != '\0') {
|
||||||
|
char c1;
|
||||||
|
while(true) {
|
||||||
|
c1 = str1[one];
|
||||||
|
if(c1 == '\0' || Character.isAlphabetic(c1) || Character.isDigit(c1)) break;
|
||||||
|
one++;
|
||||||
|
}
|
||||||
|
|
||||||
|
char c2;
|
||||||
|
while(true) {
|
||||||
|
c2 = str2[two];
|
||||||
|
if(c2 == '\0' || Character.isAlphabetic(c2) || Character.isDigit(c2)) break;
|
||||||
|
two++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we ran to the end of either, we are finished with the loop
|
||||||
|
if (c1 == '\0' || c2 == '\0') break;
|
||||||
|
|
||||||
|
// If the separator lengths were different, we are also finished
|
||||||
|
if ((one - ptr1) != (two - ptr2)) {
|
||||||
|
return (one - ptr1) < (two - ptr2) ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr1 = one;
|
||||||
|
ptr2 = two;
|
||||||
|
|
||||||
|
// grab first completely alpha or completely numeric segment
|
||||||
|
// leave one and two pointing to the start of the alpha or numeric
|
||||||
|
// segment and walk ptr1 and ptr2 to end of segment
|
||||||
|
if (Character.isDigit(str1[ptr1])) {
|
||||||
|
while(true) {
|
||||||
|
c1 = str1[ptr1];
|
||||||
|
if(c1 == '\0' || !Character.isDigit(c1)) break;
|
||||||
|
ptr1++;
|
||||||
|
}
|
||||||
|
while(true) {
|
||||||
|
c2 = str2[ptr2];
|
||||||
|
if(c2 == '\0' || !Character.isDigit(c2)) break;
|
||||||
|
ptr2++;
|
||||||
|
}
|
||||||
|
isNum = true;
|
||||||
|
} else {
|
||||||
|
while(true) {
|
||||||
|
c1 = str1[ptr1];
|
||||||
|
if(c1 == '\0' || !Character.isAlphabetic(c1)) break;
|
||||||
|
ptr1++;
|
||||||
|
}
|
||||||
|
while(true) {
|
||||||
|
c2 = str2[ptr2];
|
||||||
|
if(c2 == '\0' || !Character.isAlphabetic(c2)) break;
|
||||||
|
ptr2++;
|
||||||
|
}
|
||||||
|
isNum = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// save character at the end of the alpha or numeric segment
|
||||||
|
// so that they can be restored after the comparison
|
||||||
|
oldch1 = str1[ptr1];
|
||||||
|
str1[ptr1] = '\0';
|
||||||
|
oldch2 = str2[ptr2];
|
||||||
|
str2[ptr2] = '\0';
|
||||||
|
|
||||||
|
// this cannot happen, as we previously tested to make sure that
|
||||||
|
// the first string has a non-null segment
|
||||||
|
if (one == ptr1) {
|
||||||
|
return -1; // arbitrary
|
||||||
|
}
|
||||||
|
|
||||||
|
// take care of the case where the two version segments are
|
||||||
|
// different types: one numeric, the other alpha (i.e. empty)
|
||||||
|
// numeric segments are always newer than alpha segments
|
||||||
|
// XXX See patch #60884 (and details) from bugzilla #50977.
|
||||||
|
if (two == ptr2) {
|
||||||
|
return isNum ? 1 : -1;
|
||||||
|
}
|
||||||
|
if (isNum) {
|
||||||
|
/* this used to be done by converting the digit segments */
|
||||||
|
/* to ints using atoi() - it's changed because long */
|
||||||
|
/* digit segments can overflow an int - this should fix that. */
|
||||||
|
|
||||||
|
/* throw away any leading zeros - it's a number, right? */
|
||||||
|
while (str1[one] == '0') one++;
|
||||||
|
while (str2[two] == '0') two++;
|
||||||
|
|
||||||
|
/* whichever number has more digits wins */
|
||||||
|
int len1 = strlen(str1, one);
|
||||||
|
int len2 = strlen(str2, two);
|
||||||
|
if (len1 > len2) {
|
||||||
|
return 1;
|
||||||
|
} else if (len2 > len1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// strcmp will return which one is greater - even if the two
|
||||||
|
// segments are alpha or if they are numeric. don't return
|
||||||
|
// if they are equal because there might be more segments to
|
||||||
|
// compare
|
||||||
|
int rc = strcmp(str1, one, str2, two);
|
||||||
|
if (rc != 0) return rc;
|
||||||
|
|
||||||
|
// restore character that was replaced by null above
|
||||||
|
str1[ptr1] = oldch1;
|
||||||
|
one = ptr1;
|
||||||
|
str2[ptr2] = oldch2;
|
||||||
|
two = ptr2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this catches the case where all numeric and alpha segments have
|
||||||
|
// compared identically but the segment separating characters were
|
||||||
|
// different
|
||||||
|
if (str1[one] == '\0' && str2[two] == '\0') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the final showdown. we never want a remaining alpha string to
|
||||||
|
* beat an empty string. the logic is a bit weird, but:
|
||||||
|
* - if one is empty and two is not an alpha, two is newer.
|
||||||
|
* - if one is an alpha, two is newer.
|
||||||
|
* - otherwise one is newer.
|
||||||
|
*/
|
||||||
|
if ((str1[one] == '\0' && !Character.isAlphabetic(str2[two]))
|
||||||
|
|| Character.isAlphabetic(str1[one])) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static char[] defaultEpoch = new char[] {'0'};
|
||||||
|
|
||||||
|
public static int cmp(String v1, String v2) {
|
||||||
|
if(v1 == null && v2 == null) return 0;
|
||||||
|
else if(v1 == null) return -1;
|
||||||
|
else if(v2 == null) return 1;
|
||||||
|
else if(Objects.equals(v1, v2)) return 0;
|
||||||
|
|
||||||
|
char[] chars1 = cstring(v1);
|
||||||
|
char[] chars2 = cstring(v2);
|
||||||
|
int[] evr = new int[3];
|
||||||
|
parseEVR(chars1, evr);
|
||||||
|
int epoch1 = evr[0];
|
||||||
|
int version1 = evr[1];
|
||||||
|
int release1 = evr[2];
|
||||||
|
parseEVR(chars2, evr);
|
||||||
|
int epoch2 = evr[0];
|
||||||
|
int version2 = evr[1];
|
||||||
|
int release2 = evr[2];
|
||||||
|
|
||||||
|
char[] seq1 = epoch1 == -1 ? defaultEpoch : chars1;
|
||||||
|
char[] seq2 = epoch2 == -1 ? defaultEpoch : chars2;
|
||||||
|
int ret = rpmvercmp(seq1, epoch1 == -1 ? 0 : epoch1, seq2, epoch2 == -1 ? 0 : epoch2);
|
||||||
|
if(ret == 0) {
|
||||||
|
ret = rpmvercmp(chars1, version1, chars2, version2);
|
||||||
|
if(ret == 0 && release1 != -1 && release2 != -1) {
|
||||||
|
ret = rpmvercmp(chars1, release1, chars2, release2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(String v1, String v2) {
|
||||||
|
return cmp(v1, v2);
|
||||||
|
}
|
||||||
|
}
|
40
src/test/java/net/woggioni/jwo/VersionComparatorTest.java
Normal file
40
src/test/java/net/woggioni/jwo/VersionComparatorTest.java
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package net.woggioni.jwo;
|
||||||
|
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.ArgumentsProvider;
|
||||||
|
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class VersionComparatorTest {
|
||||||
|
|
||||||
|
private static class TestCaseProvider implements ArgumentsProvider {
|
||||||
|
@Override
|
||||||
|
@SneakyThrows
|
||||||
|
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of("", "", 0),
|
||||||
|
Arguments.of("asdfg-2019", "asdfg-2019", 0),
|
||||||
|
Arguments.of("1.2", "1", 1),
|
||||||
|
Arguments.of("1.2", "1.0", 1),
|
||||||
|
Arguments.of("1.2", "1.10", -1),
|
||||||
|
Arguments.of("5.9.12.arch1-1", "5.8.0.arch1-1", 1),
|
||||||
|
Arguments.of("5.9.12.arch1-1", "5.10.0.arch1-1", -1),
|
||||||
|
Arguments.of("5.10.0.arch1-1", "5.10.0.arch1-3", -1),
|
||||||
|
Arguments.of("5.10.0.arch1-10", "5.10.0.arch1-3", 1),
|
||||||
|
Arguments.of("5.9.0.arch1-10", "5.10.0.arch1-3", -1),
|
||||||
|
Arguments.of("20191220.6871bff-1", "20201120.bc9cd0b-1", -1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest(name="version1: \"{0}\", version2: \"{1}\", expected outcome: {2}")
|
||||||
|
@ArgumentsSource(TestCaseProvider.class)
|
||||||
|
public void test(String version1, String version2, int expectedOutcome) {
|
||||||
|
Assertions.assertEquals(expectedOutcome, VersionComparator.cmp(version1, version2));
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user