| /* |
| * 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.sis.util; |
| |
| import java.io.Serializable; |
| import java.util.StringTokenizer; |
| |
| import static org.apache.sis.internal.system.Modules.MAJOR_VERSION; |
| import static org.apache.sis.internal.system.Modules.MINOR_VERSION; |
| |
| |
| /** |
| * Holds a version number as a sequence of strings separated by either a dot or a dash. |
| * The first three strings, usually numbers, are called respectively {@linkplain #getMajor() major}, |
| * {@linkplain #getMinor() minor} and {@linkplain #getRevision() revision}. |
| * For example a version code such as {@code "6.11.2"} will have major number 6, minor |
| * number 11 and revision number 2. Alternatively a version code such as {@code "3.18-SNAPSHOT"} |
| * will have major version number 3, minor version number 18 and revision string "SNAPSHOT". |
| * |
| * <p>This class provides methods for performing comparisons of {@code Version} objects where major, |
| * minor and revision parts are compared as numbers when possible, or as strings otherwise.</p> |
| * |
| * <h2>Immutability and thread safety</h2> |
| * This class is immutable and thus inherently thread-safe. |
| * Subclasses may or may not be immutable, at implementation choice. But implementers are |
| * encouraged to make sure that subclasses remain immutable for more predictable behavior. |
| * |
| * @author Martin Desruisseaux (IRD, Geomatys) |
| * @version 0.4 |
| * @since 0.3 |
| * @module |
| */ |
| public class Version implements CharSequence, Comparable<Version>, Serializable { |
| /** |
| * For cross-version compatibility. |
| */ |
| private static final long serialVersionUID = 8402041502662929792L; |
| |
| /** |
| * The separator characters between {@linkplain #getMajor() major}, {@linkplain #getMinor() minor} |
| * and {@linkplain #getRevision() revision} components. Any character in this string fits. |
| */ |
| private static final String SEPARATORS = ".-"; |
| |
| /** |
| * The version of this Apache SIS distribution. |
| */ |
| public static final Version SIS = new Version(MAJOR_VERSION + "." + MINOR_VERSION + "-SNAPSHOT"); |
| |
| /** |
| * A few commonly used version numbers. This list is based on SIS needs, e.g. in {@code DataStore} implementations. |
| * New constants are likely to be added in any future SIS versions. |
| * |
| * @see #valueOf(int[]) |
| */ |
| private static final Version[] CONSTANTS = { |
| new Version("1"), |
| new Version("2"), |
| new Version("1.0"), |
| new Version("1.1") |
| }; |
| |
| /** |
| * The version in string form, with leading and trailing spaces removed. |
| */ |
| private final String version; |
| |
| /** |
| * The components of the version string. Will be created when first needed. |
| */ |
| private transient String[] components; |
| |
| /** |
| * The parsed components of the version string. Will be created when first needed. |
| */ |
| private transient Comparable<?>[] parsed; |
| |
| /** |
| * The hash code value. Will be computed when first needed. |
| */ |
| private transient int hashCode; |
| |
| /** |
| * Creates a new version object from the supplied string. |
| * |
| * @param version the version as a string. |
| */ |
| public Version(final String version) { |
| ArgumentChecks.ensureNonNull("version", version); |
| this.version = version; |
| } |
| |
| /** |
| * Returns an instance for the given integer values. |
| * The {@code components} array must contain at least 1 element, where: |
| * |
| * <ul> |
| * <li>The first element is the {@linkplain #getMajor() major} number.</li> |
| * <li>The second element (if any) is the {@linkplain #getMinor() minor} number.</li> |
| * <li>The third element (if any) is the {@linkplain #getRevision() revision} number.</li> |
| * <li>Other elements (if any) will be appended to the {@link #toString() string value}.</li> |
| * </ul> |
| * |
| * @param components the major number, optionally followed by minor, revision or other numbers. |
| * @return a new or existing instance of {@code Version} for the given numbers. |
| * |
| * @since 0.4 |
| */ |
| public static Version valueOf(final int... components) { |
| ArgumentChecks.ensureNonEmpty("components", components, Integer.MIN_VALUE, Integer.MAX_VALUE, false); |
| final Version version; |
| final int major = components[0]; |
| if (components.length == 1) { |
| if (major >= 1 && major <= CONSTANTS.length) { |
| return CONSTANTS[major - 1]; |
| } else { |
| version = new Version(Integer.toString(major)); |
| } |
| } else { |
| final StringBuilder buffer = new StringBuilder().append(major); |
| for (int i=1; i<components.length; i++) { |
| buffer.append('.').append(components[i]); |
| } |
| version = new Version(buffer.toString()); |
| } |
| /* |
| * Pre-compute the 'parsed' array since we already have the integer values. It will avoid the need to |
| * create the 'this.components' array and to parse the String values if a 'getFoo()' method is invoked. |
| * Note that the cost is typically only the 'parsed' array creation, not Integer objects creation, since |
| * version numbers are usually small enough for allowing 'Integer.valueOf(int)' to cache them. |
| */ |
| final Integer[] parsed = new Integer[components.length]; |
| for (int i=0; i<components.length; i++) { |
| parsed[i] = components[i]; |
| } |
| version.parsed = parsed; |
| return version; |
| } |
| |
| /** |
| * Returns the major version number. This method returns an {@link Integer} if possible, |
| * or a {@link String} otherwise. |
| * |
| * @return the major version number. |
| */ |
| public Comparable<?> getMajor() { |
| return getComponent(0); |
| } |
| |
| /** |
| * Returns the minor version number. This method returns an {@link Integer} if possible, |
| * or a {@link String} otherwise. If there is no minor version number, then this method |
| * returns {@code null}. |
| * |
| * @return the minor version number, or {@code null} if none. |
| */ |
| public Comparable<?> getMinor() { |
| return getComponent(1); |
| } |
| |
| /** |
| * Returns the revision number. This method returns an {@link Integer} if possible, |
| * or a {@link String} otherwise. If there is no revision number, then this method |
| * returns {@code null}. |
| * |
| * @return the revision number, or {@code null} if none. |
| */ |
| public Comparable<?> getRevision() { |
| return getComponent(2); |
| } |
| |
| /** |
| * Returns the specified components of this version string. For a version of the |
| * {@code major.minor.revision} form, index 0 stands for the major version number, |
| * 1 stands for the minor version number and 2 stands for the revision number. |
| * |
| * <p>The return value is an {@link Integer} if the component is parsable as an integer, |
| * or a {@link String} otherwise. If there is no component at the specified index, |
| * then this method returns {@code null}.</p> |
| * |
| * @param index the index of the component to fetch. |
| * @return the value at the specified index, or {@code null} if none. |
| * @throws IndexOutOfBoundsException if {@code index} is negative. |
| */ |
| final synchronized Comparable<?> getComponent(final int index) { |
| if (parsed == null) { |
| if (components == null) { |
| final StringTokenizer tokens = new StringTokenizer(version, SEPARATORS); |
| components = new String[tokens.countTokens()]; |
| for (int i=0; tokens.hasMoreTokens(); i++) { |
| components[i] = tokens.nextToken(); |
| } |
| } |
| parsed = new Comparable<?>[components.length]; |
| } |
| if (index >= parsed.length) { |
| return null; |
| } |
| Comparable<?> candidate = parsed[index]; |
| if (candidate == null) { |
| final String value = CharSequences.trimWhitespaces(components[index]); |
| try { |
| candidate = Integer.valueOf(value); |
| } catch (NumberFormatException e) { |
| candidate = value; |
| } |
| parsed[index] = candidate; |
| } |
| return candidate; |
| } |
| |
| /** |
| * Get the rank of the specified object according this type. |
| * This is for {@link #compareTo(Version, int)} internal only. |
| */ |
| private static int getTypeRank(final Object value) { |
| if (value instanceof CharSequence) { |
| return 0; |
| } |
| if (value instanceof Number) { |
| return 1; |
| } |
| throw new IllegalArgumentException(String.valueOf(value)); |
| } |
| |
| /** |
| * Compares this version with an other version object, up to the specified limit. A limit |
| * of 1 compares only the {@linkplain #getMajor() major} version number. A limit of 2 compares |
| * the major and {@linkplain #getMinor() minor} version numbers, <i>etc</i>. |
| * The comparisons are performed as {@link Integer} object if possible, or as {@link String} |
| * otherwise. |
| * |
| * @param other the other version object to compare with. |
| * @param limit the maximum number of components to compare. |
| * @return a negative value if this version is lower than the supplied version, |
| * a positive value if it is higher, or 0 if they are equal. |
| */ |
| public int compareTo(final Version other, final int limit) { |
| ArgumentChecks.ensureNonNull ("other", other); |
| ArgumentChecks.ensurePositive("limit", limit); |
| for (int i=0; i<limit; i++) { |
| final Comparable<?> v1 = this.getComponent(i); |
| final Comparable<?> v2 = other.getComponent(i); |
| if (v1 == null) { |
| return (v2 == null) ? 0 : -1; |
| } else if (v2 == null) { |
| return +1; |
| } |
| final int dr = getTypeRank(v1) - getTypeRank(v2); |
| if (dr != 0) { |
| /* |
| * One value is a text while the other value is a number. We could be tempted to |
| * force a comparison by converting the number to a String and then invoking the |
| * String.compareTo(String) method, but this strategy would violate the following |
| * contract from Comparable.compareTo(Object): "The implementer must also ensure |
| * that the relation is transitive". Use case: |
| * |
| * A is the integer 10 |
| * B is the string "8Z" |
| * C is the integer 5. |
| * |
| * If mismatched types are converted to String before being compared, then we |
| * would have A < B < C. Transitivity implies that A < C, but if we compare A |
| * and C directly we get A > C because they are compared as numbers. An easy |
| * way to fix this inconsistency is to define all String as lexicographically |
| * preceding Integer, no matter their content. This is what we do here. |
| */ |
| return dr; |
| } |
| @SuppressWarnings({"unchecked","rawtypes"}) |
| final int c = ((Comparable) v1).compareTo(v2); |
| if (c != 0) { |
| return c; |
| } |
| } |
| return 0; |
| } |
| |
| /** |
| * Compares this version with an other version object. This method performs the same |
| * comparison than {@link #compareTo(Version, int)} with no limit. |
| * |
| * @param other the other version object to compare with. |
| * @return a negative value if this version is lower than the supplied version, |
| * a positive value if it is higher, or 0 if they are equal. |
| */ |
| @Override |
| public int compareTo(final Version other) { |
| return compareTo(other, Integer.MAX_VALUE); |
| } |
| |
| /** |
| * Compare this version string with the specified object for equality. Two version are |
| * considered equal if <code>{@linkplain #compareTo(Object) compareTo}(other) == 0</code>. |
| * |
| * @param other the object to compare with this version for equality. |
| */ |
| @Override |
| public boolean equals(final Object other) { |
| if (other != null && getClass() == other.getClass()) { |
| return compareTo((Version) other) == 0; |
| } |
| return false; |
| } |
| |
| /** |
| * Returns the length of the version string. |
| */ |
| @Override |
| public int length() { |
| return version.length(); |
| } |
| |
| /** |
| * Returns the {@code char} value at the specified index. |
| */ |
| @Override |
| public char charAt(final int index) { |
| return version.charAt(index); |
| } |
| |
| /** |
| * Returns a new version string that is a subsequence of this sequence. |
| */ |
| @Override |
| public CharSequence subSequence(final int start, final int end) { |
| return version.subSequence(start, end); |
| } |
| |
| /** |
| * Returns the version string. This is the string specified at construction time. |
| */ |
| @Override |
| public String toString() { |
| return version; |
| } |
| |
| /** |
| * Returns a hash code value for this version. |
| */ |
| @Override |
| public int hashCode() { |
| if (hashCode == 0) { |
| int code = (int) serialVersionUID; |
| int index = 0; |
| Comparable<?> component; |
| while ((component = getComponent(index)) != null) { |
| code = code * 31 + component.hashCode(); |
| index++; |
| } |
| if (code == 0) { |
| code = -1; |
| } |
| hashCode = code; |
| } |
| return hashCode; |
| } |
| } |