org.wildfly.plugin.tools.VersionComparator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of wildfly-plugin-tools Show documentation
Show all versions of wildfly-plugin-tools Show documentation
A group of tools for interacting/managing with a WildFly container
The newest version!
/*
* Copyright The WildFly Authors
* SPDX-License-Identifier: Apache-2.0
*/
package org.wildfly.plugin.tools;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
/**
* Compares two versions. The comparison is case-insensitive.
*
* Some qualifiers map to other qualifiers. Below is a table of those mappings.
*
*
* Qualifier
* Mapping
*
*
* GA
* Final
*
*
* a
* Alpha
*
*
* b
* Beta
*
*
* m
* Milestone
*
*
* cr
* rc
*
*
*
*
* @author James R. Perkins
*/
public class VersionComparator implements Comparator {
private static final VersionComparator INSTANCE = new VersionComparator();
private static final VersionComparator IGNORE_INSTANCE = new VersionComparator(true);
private final boolean ignoreSnapshots;
/**
* Creates a new version comparator.
*/
public VersionComparator() {
this(false);
}
private VersionComparator(final boolean ignoreSnapshots) {
this.ignoreSnapshots = ignoreSnapshots;
}
/**
* Returns an instance of a version comparator.
*
* @return a version comparator instance
*/
public static VersionComparator getInstance() {
return getInstance(false);
}
/**
* Returns an instance of a version comparator which optionally ignore the SNAPSHOT release extension. This can
* be useful for cases where you want to compare a version is at least a base version, not caring if it's a
* SNAPSHOT.
*
* @param ignoreSnapshots {@code true} to ignore the SNAPSHOT release extension, otherwise {@code false} which
* values a SNAPSHOT dependency less than a non-SNAPSHOT of the same version
*
* @return a version comparator instance
*/
public static VersionComparator getInstance(final boolean ignoreSnapshots) {
return ignoreSnapshots ? IGNORE_INSTANCE : INSTANCE;
}
/**
* Compares the first version against the second version.
*
* @param v1 first version
* @param v2 second version
*
* @return {@code 0} if the versions are equal, {@code -1} if version first version is less than the second version
* or {@code 1} if the first version is greater than the second version
*
* @see Comparator#compare(Object, Object)
*/
public static int compareVersion(final String v1, final String v2) {
return compareVersion(false, v1, v2);
}
/**
* Compares the first version against the second version optionally ignoring if either version has a SNAPSHOT
* release extension. This can be useful for cases where you want to compare a version is at least a base version,
* not caring if it's a SNAPSHOT.
*
*
* If {@code ignoreSnapshots} is {@code true}, the version {@code 1.0.0.Final} and {@code 1.0.0.Final-SNAPSHOT} are
* said to be equal. If set to {@code false}, {@code 1.0.0.Final} is greater than {@code 1.0.0.Final-SNAPSHOT}.
*
*
* @param ignoreSnapshots {@code true} to ignore the SNAPSHOT release extension, otherwise {@code false} which
* values a SNAPSHOT dependency less than a non-SNAPSHOT of the same version
* @param v1 the first version
* @param v2 the second version
*
* @return {@code 0} if the versions are equal, {@code -1} if version first version is less than the second version
* or {@code 1} if the first version is greater than the second version
*/
public static int compareVersion(final boolean ignoreSnapshots, final String v1, final String v2) {
// If the strings are equal ignoring the case, we can assume these are equal
if (Objects.requireNonNull(v1).equalsIgnoreCase(v2)) {
return 0;
}
final Version version1 = Version.parse(v1, ignoreSnapshots);
final Version version2 = Version.parse(Objects.requireNonNull(v2), ignoreSnapshots);
// Ensures the result is always 0, 1 or -1
return Integer.compare(version1.compareTo(version2), 0);
}
@Override
public int compare(final String o1, final String o2) {
return compareVersion(ignoreSnapshots, o1, o2);
}
private enum ReleaseType {
UNKNOWN(null),
SNAPSHOT("snapshot"),
ALPHA("alpha", "a"),
BETA("beta", "b"),
MILESTONE("milestone", "m"),
RELEASE_CANDIDATE("rc", "cr"),
FINAL("final", "", "ga"),
;
private static final Map ENTRIES;
static {
final Map map = new HashMap<>();
for (ReleaseType r : values()) {
if (r == UNKNOWN)
continue;
map.put(r.type, r);
map.put("-" + r.type, r);
for (String alias : r.aliases) {
map.put(alias, r);
}
}
ENTRIES = Map.copyOf(map);
}
private final String type;
private final List aliases;
ReleaseType(final String type, final String... aliases) {
this.type = type;
this.aliases = List.of(aliases);
}
static ReleaseType find(final String s) {
return ENTRIES.getOrDefault(s, UNKNOWN);
}
}
private static class Version implements Comparable {
private final List parts;
private final String original;
private Version(final String original, final List parts) {
this.original = original;
this.parts = parts;
}
static Version parse(final String version, final boolean ignoreSnapshot) {
final List parts = new ArrayList<>();
final StringBuilder sb = new StringBuilder();
boolean isDigit = false;
for (char c : version.toCharArray()) {
switch (c) {
case '-':
case '.': {
if (isDigit) {
parts.add(new IntegerPart(Integer.parseInt(sb.toString())));
} else {
addStringPart(parts, sb, ignoreSnapshot);
}
sb.setLength(0);
isDigit = false;
continue;
}
default: {
if (Character.isDigit(c)) {
if (!isDigit && sb.length() > 0) {
addStringPart(parts, sb, ignoreSnapshot);
sb.setLength(0);
}
isDigit = true;
} else {
if (isDigit && sb.length() > 0) {
parts.add(new IntegerPart(Integer.parseInt(sb.toString())));
sb.setLength(0);
}
isDigit = false;
}
sb.append(c);
}
}
}
if (sb.length() > 0) {
if (isDigit) {
parts.add(new IntegerPart(Integer.parseInt(sb.toString())));
} else {
addStringPart(parts, sb, ignoreSnapshot);
}
}
return new Version(version, List.copyOf(parts));
}
private static void addStringPart(final Collection parts, final StringBuilder sb, final boolean ignoreSnapshot) {
final var value = sb.toString();
if (!(ignoreSnapshot && ReleaseType.SNAPSHOT.type.equalsIgnoreCase(value))) {
parts.add(new StringPart(value));
}
}
@Override
public int compareTo(final Version o) {
final Iterator left = parts.iterator();
final Iterator right = o.parts.iterator();
int result = 0;
while (left.hasNext() || right.hasNext()) {
if (left.hasNext() && right.hasNext()) {
result = left.next().compareTo(right.next());
} else if (left.hasNext()) {
result = left.next().compareTo(NULL_PART);
} else {
// Need the inverse of the comparison
result = (-1 * right.next().compareTo(NULL_PART));
}
if (result != 0) {
break;
}
}
return result;
}
@Override
public int hashCode() {
return 33 * (17 + original.hashCode());
}
@Override
public boolean equals(final Object obj) {
if (obj == this)
return true;
if (!(obj instanceof Version)) {
return false;
}
final Version other = (Version) obj;
return Objects.equals(original, other.original);
}
@Override
public String toString() {
return original;
}
@FunctionalInterface
private interface Part extends Comparable {
}
private static final Part NULL_PART = o -> {
throw new UnsupportedOperationException();
};
private static class IntegerPart implements Part {
private final Integer value;
private IntegerPart(final Integer value) {
this.value = value;
}
@Override
public int compareTo(final Part o) {
if (o == NULL_PART) {
return value.compareTo(0);
}
if (o instanceof IntegerPart) {
return value.compareTo(((IntegerPart) o).value);
}
return 1;
}
@Override
public String toString() {
return value.toString();
}
}
private static class StringPart implements Part {
private final String originalValue;
private final String value;
private final ReleaseType releaseType;
private StringPart(final String value) {
originalValue = value;
this.value = value.toLowerCase(Locale.ROOT);
releaseType = ReleaseType.find(this.value);
}
@Override
public int compareTo(final Part o) {
if (o == NULL_PART) {
return releaseType.compareTo(ReleaseType.FINAL);
}
if (o instanceof StringPart) {
if (releaseType == ReleaseType.UNKNOWN && ((StringPart) o).releaseType == ReleaseType.UNKNOWN) {
return value.compareTo(((StringPart) o).value);
}
return releaseType.compareTo(((StringPart) o).releaseType);
}
return -1;
}
@Override
public String toString() {
return originalValue;
}
}
}
}