Repository: camel Updated Branches: refs/heads/camel-2.16.x 3e0fd2b1c -> 267c297c9 refs/heads/master 659664a45 -> 9ce810f30
[CAMEL-9500] CamelVersionHelper cannot handle version qualifier Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/267c297c Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/267c297c Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/267c297c Branch: refs/heads/camel-2.16.x Commit: 267c297c9a631acf124a4dc6bb1216b150940524 Parents: 3e0fd2b Author: Thomas Diesler <thomas.dies...@jboss.com> Authored: Mon Jan 11 13:52:21 2016 +0100 Committer: Claus Ibsen <davscl...@apache.org> Committed: Tue Jan 12 15:27:47 2016 +0100 ---------------------------------------------------------------------- .../apache/camel/util/CamelVersionHelper.java | 406 ++++++++++++++++++- .../camel/util/CamelVersionHelperTest.java | 2 + 2 files changed, 397 insertions(+), 11 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/267c297c/camel-core/src/main/java/org/apache/camel/util/CamelVersionHelper.java ---------------------------------------------------------------------- diff --git a/camel-core/src/main/java/org/apache/camel/util/CamelVersionHelper.java b/camel-core/src/main/java/org/apache/camel/util/CamelVersionHelper.java index 5c630b2..4fa7d40 100644 --- a/camel-core/src/main/java/org/apache/camel/util/CamelVersionHelper.java +++ b/camel-core/src/main/java/org/apache/camel/util/CamelVersionHelper.java @@ -16,30 +16,414 @@ */ package org.apache.camel.util; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Properties; +import java.util.Stack; + /** * A simple util to test Camel versions. */ public final class CamelVersionHelper { + private CamelVersionHelper() { - //utility class, never constructed + // utility class, never constructed } /** * Checks whether other >= base * - * @param base the base version + * @param base the base version * @param other the other version * @return <tt>true</tt> if GE, <tt>false</tt> otherwise */ public static boolean isGE(String base, String other) { - String s1 = base.replaceAll("\\.", ""); - String s2 = other.replaceAll("\\.", ""); - // SNAPSHOT as .0 - s1 = s1.replace("-SNAPSHOT", "0"); - s2 = s2.replace("-SNAPSHOT", "0"); - // then use number comparison - int n1 = Integer.valueOf(s1); - int n2 = Integer.valueOf(s2); - return Integer.compare(n2, n1) >= 0; + ComparableVersion v1 = new ComparableVersion(base); + ComparableVersion v2 = new ComparableVersion(other); + return v2.compareTo(v1) >= 0; + } + + /** + * Generic implementation of version comparison. + * https://github.com/apache/maven/blob/master/maven-artifact/src/main/java/ + * org/apache/maven/artifact/versioning/ComparableVersion.java + * <p> + * Features: + * <ul> + * <li>mixing of '<code>-</code>' (hyphen) and '<code>.</code>' (dot) + * separators,</li> + * <li>transition between characters and digits also constitutes a + * separator: <code>1.0alpha1 => [1, 0, alpha, 1]</code></li> + * <li>unlimited number of version components,</li> + * <li>version components in the text can be digits or strings,</li> + * <li>strings are checked for well-known qualifiers and the qualifier + * ordering is used for version ordering. Well-known qualifiers (case + * insensitive) are: + * <ul> + * <li><code>alpha</code> or <code>a</code></li> + * <li><code>beta</code> or <code>b</code></li> + * <li><code>milestone</code> or <code>m</code></li> + * <li><code>rc</code> or <code>cr</code></li> + * <li><code>snapshot</code></li> + * <li><code>(the empty string)</code> or <code>ga</code> or + * <code>final</code></li> + * <li><code>sp</code></li> + * </ul> + * Unknown qualifiers are considered after known qualifiers, with lexical + * order (always case insensitive),</li> + * <li>a hyphen usually precedes a qualifier, and is always less important + * than something preceded with a dot.</li> + * </ul> + * </p> + * + * @see <a href= + * "https://cwiki.apache.org/confluence/display/MAVENOLD/Versioning"> + * "Versioning" on Maven Wiki</a> + * @author <a href="mailto:ken...@apache.org">Kenney Westerhof</a> + * @author <a href="mailto:hbout...@apache.org">Hervé Boutemy</a> + */ + private static final class ComparableVersion implements Comparable<ComparableVersion> { + + private String value; + private String canonical; + private ListItem items; + + private interface Item { + int INTEGER_ITEM = 0; + int STRING_ITEM = 1; + int LIST_ITEM = 2; + + int compareTo(Item item); + + int getType(); + + boolean isNull(); + } + + /** + * Represents a numeric item in the version item list. + */ + private static class IntegerItem implements Item { + + private static final BigInteger BIG_INTEGER_ZERO = new BigInteger("0"); + private static final IntegerItem ZERO = new IntegerItem(); + private final BigInteger value; + + private IntegerItem() { + this.value = BIG_INTEGER_ZERO; + } + + public IntegerItem(String str) { + this.value = new BigInteger(str); + } + + public int getType() { + return INTEGER_ITEM; + } + + public boolean isNull() { + return BIG_INTEGER_ZERO.equals(value); + } + + public int compareTo(Item item) { + if (item == null) { + return BIG_INTEGER_ZERO.equals(value) ? 0 : 1; // 1.0 == 1, + // 1.1 > 1 + } + + switch (item.getType()) { + case INTEGER_ITEM: + return value.compareTo(((IntegerItem)item).value); + + case STRING_ITEM: + return 1; // 1.1 > 1-sp + + case LIST_ITEM: + return 1; // 1.1 > 1-1 + + default: + throw new RuntimeException("invalid item: " + item.getClass()); + } + } + + public String toString() { + return value.toString(); + } + } + + /** + * Represents a string in the version item list, usually a qualifier. + */ + private static class StringItem implements Item { + private static final String[] QUALIFIERS = {"alpha", "beta", "milestone", "rc", "snapshot", "", "sp"}; + + private static final List<String> QUALIFIERS_LIST = Arrays.asList(QUALIFIERS); + + private static final Properties ALIASES = new Properties(); + + static { + ALIASES.put("ga", ""); + ALIASES.put("final", ""); + ALIASES.put("cr", "rc"); + } + + /** + * A comparable value for the empty-string qualifier. This one is + * used to determine if a given qualifier makes the version older + * than one without a qualifier, or more recent. + */ + private static final String RELEASE_VERSION_INDEX = String.valueOf(QUALIFIERS_LIST.indexOf("")); + + private String value; + + public StringItem(String value, boolean followedByDigit) { + if (followedByDigit && value.length() == 1) { + // a1 = alpha-1, b1 = beta-1, m1 = milestone-1 + switch (value.charAt(0)) { + case 'a': + value = "alpha"; + break; + case 'b': + value = "beta"; + break; + case 'm': + value = "milestone"; + break; + default: + } + } + this.value = ALIASES.getProperty(value, value); + } + + public int getType() { + return STRING_ITEM; + } + + public boolean isNull() { + return comparableQualifier(value).compareTo(RELEASE_VERSION_INDEX) == 0; + } + + /** + * Returns a comparable value for a qualifier. This method takes + * into account the ordering of known qualifiers then unknown + * qualifiers with lexical ordering. just returning an Integer with + * the index here is faster, but requires a lot of if/then/else to + * check for -1 or QUALIFIERS.size and then resort to lexical + * ordering. Most comparisons are decided by the first character, so + * this is still fast. If more characters are needed then it + * requires a lexical sort anyway. + * + * @param qualifier + * @return an equivalent value that can be used with lexical + * comparison + */ + public static String comparableQualifier(String qualifier) { + int i = QUALIFIERS_LIST.indexOf(qualifier); + + return i == -1 ? (QUALIFIERS_LIST.size() + "-" + qualifier) : String.valueOf(i); + } + + public int compareTo(Item item) { + if (item == null) { + // 1-rc < 1, 1-ga > 1 + return comparableQualifier(value).compareTo(RELEASE_VERSION_INDEX); + } + switch (item.getType()) { + case INTEGER_ITEM: + return -1; // 1.any < 1.1 ? + + case STRING_ITEM: + return comparableQualifier(value).compareTo(comparableQualifier(((StringItem)item).value)); + + case LIST_ITEM: + return -1; // 1.any < 1-1 + + default: + throw new RuntimeException("invalid item: " + item.getClass()); + } + } + + public String toString() { + return value; + } + } + + /** + * Represents a version list item. This class is used both for the + * global item list and for sub-lists (which start with '-(number)' in + * the version specification). + */ + @SuppressWarnings("serial") + private static class ListItem extends ArrayList<Item> implements Item { + public int getType() { + return LIST_ITEM; + } + + public boolean isNull() { + return size() == 0; + } + + void normalize() { + for (int i = size() - 1; i >= 0; i--) { + Item lastItem = get(i); + + if (lastItem.isNull()) { + // remove null trailing items: 0, "", empty list + remove(i); + } else if (!(lastItem instanceof ListItem)) { + break; + } + } + } + + public int compareTo(Item item) { + if (item == null) { + if (size() == 0) { + return 0; // 1-0 = 1- (normalize) = 1 + } + Item first = get(0); + return first.compareTo(null); + } + switch (item.getType()) { + case INTEGER_ITEM: + return -1; // 1-1 < 1.0.x + + case STRING_ITEM: + return 1; // 1-1 > 1-sp + + case LIST_ITEM: + Iterator<Item> left = iterator(); + Iterator<Item> right = ((ListItem)item).iterator(); + + while (left.hasNext() || right.hasNext()) { + Item l = left.hasNext() ? left.next() : null; + Item r = right.hasNext() ? right.next() : null; + + // if this is shorter, then invert the compare and mul + // with -1 + int result = l == null ? (r == null ? 0 : -1 * r.compareTo(l)) : l.compareTo(r); + + if (result != 0) { + return result; + } + } + + return 0; + + default: + throw new RuntimeException("invalid item: " + item.getClass()); + } + } + + public String toString() { + StringBuilder buffer = new StringBuilder(); + for (Item item : this) { + if (buffer.length() > 0) { + buffer.append((item instanceof ListItem) ? '-' : '.'); + } + buffer.append(item); + } + return buffer.toString(); + } + } + + private ComparableVersion(String version) { + parseVersion(version); + } + + private void parseVersion(String version) { + this.value = version; + + items = new ListItem(); + + version = version.toLowerCase(Locale.ENGLISH); + + ListItem list = items; + + Stack<Item> stack = new Stack<>(); + stack.push(list); + + boolean isDigit = false; + + int startIndex = 0; + + for (int i = 0; i < version.length(); i++) { + char c = version.charAt(i); + + if (c == '.') { + if (i == startIndex) { + list.add(IntegerItem.ZERO); + } else { + list.add(parseItem(isDigit, version.substring(startIndex, i))); + } + startIndex = i + 1; + } else if (c == '-') { + if (i == startIndex) { + list.add(IntegerItem.ZERO); + } else { + list.add(parseItem(isDigit, version.substring(startIndex, i))); + } + startIndex = i + 1; + + list.add(list = new ListItem()); + stack.push(list); + } else if (Character.isDigit(c)) { + if (!isDigit && i > startIndex) { + list.add(new StringItem(version.substring(startIndex, i), true)); + startIndex = i; + + list.add(list = new ListItem()); + stack.push(list); + } + + isDigit = true; + } else { + if (isDigit && i > startIndex) { + list.add(parseItem(true, version.substring(startIndex, i))); + startIndex = i; + + list.add(list = new ListItem()); + stack.push(list); + } + + isDigit = false; + } + } + + if (version.length() > startIndex) { + list.add(parseItem(isDigit, version.substring(startIndex))); + } + + while (!stack.isEmpty()) { + list = (ListItem)stack.pop(); + list.normalize(); + } + + canonical = items.toString(); + } + + private static Item parseItem(boolean isDigit, String buf) { + return isDigit ? new IntegerItem(buf) : new StringItem(buf, false); + } + + public int compareTo(ComparableVersion o) { + return items.compareTo(o.items); + } + + public String toString() { + return value; + } + + public boolean equals(Object o) { + return (o instanceof ComparableVersion) && canonical.equals(((ComparableVersion)o).canonical); + } + + public int hashCode() { + return canonical.hashCode(); + } } } http://git-wip-us.apache.org/repos/asf/camel/blob/267c297c/camel-core/src/test/java/org/apache/camel/util/CamelVersionHelperTest.java ---------------------------------------------------------------------- diff --git a/camel-core/src/test/java/org/apache/camel/util/CamelVersionHelperTest.java b/camel-core/src/test/java/org/apache/camel/util/CamelVersionHelperTest.java index bf249ba..e2c294d 100644 --- a/camel-core/src/test/java/org/apache/camel/util/CamelVersionHelperTest.java +++ b/camel-core/src/test/java/org/apache/camel/util/CamelVersionHelperTest.java @@ -29,10 +29,12 @@ public class CamelVersionHelperTest extends TestCase { assertTrue(isGE("2.15.0", "2.15.1")); assertTrue(isGE("2.15.0", "2.16.0")); assertTrue(isGE("2.15.0", "2.16-SNAPSHOT")); + assertTrue(isGE("2.15.0", "2.16-foo")); assertFalse(isGE("2.15.0", "2.14.3")); assertFalse(isGE("2.15.0", "2.13.0")); assertFalse(isGE("2.15.0", "2.13.1")); assertFalse(isGE("2.15.0", "2.14-SNAPSHOT")); + assertFalse(isGE("2.15.0", "2.14-foo")); } }