You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2016/01/12 15:32:01 UTC

[1/2] camel git commit: [CAMEL-9500] CamelVersionHelper cannot handle version qualifier

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 <th...@jboss.com>
Authored: Mon Jan 11 13:52:21 2016 +0100
Committer: Claus Ibsen <da...@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 =&gt; [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:kenney@apache.org">Kenney Westerhof</a>
+     * @author <a href="mailto:hboutemy@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"));
     }
 }


[2/2] camel git commit: [CAMEL-9500] CamelVersionHelper cannot handle version qualifier

Posted by da...@apache.org.
[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/9ce810f3
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/9ce810f3
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/9ce810f3

Branch: refs/heads/master
Commit: 9ce810f30ebf95e944f44ea03708f0213be9147a
Parents: 659664a
Author: Thomas Diesler <th...@jboss.com>
Authored: Mon Jan 11 13:52:21 2016 +0100
Committer: Claus Ibsen <da...@apache.org>
Committed: Tue Jan 12 15:28:32 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/9ce810f3/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 =&gt; [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:kenney@apache.org">Kenney Westerhof</a>
+     * @author <a href="mailto:hboutemy@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/9ce810f3/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"));
     }
 }