| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| 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 |
| } |
| |
| /** |
| * Checks whether other >= base |
| * |
| * @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) { |
| 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: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; |
| } |
| |
| 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; |
| |
| 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(); |
| } |
| } |
| } |