| /* |
| * Copyright (c) OSGi Alliance (2004, 2016). All Rights Reserved. |
| * |
| * Licensed 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.osgi.framework; |
| |
| import java.util.NoSuchElementException; |
| import java.util.StringTokenizer; |
| |
| /** |
| * Version identifier for capabilities such as bundles and packages. |
| * |
| * <p> |
| * Version identifiers have four components. |
| * <ol> |
| * <li>Major version. A non-negative integer.</li> |
| * <li>Minor version. A non-negative integer.</li> |
| * <li>Micro version. A non-negative integer.</li> |
| * <li>Qualifier. A text string. See {@code Version(String)} for the format of |
| * the qualifier string.</li> |
| * </ol> |
| * |
| * <p> |
| * {@code Version} objects are immutable. |
| * |
| * @since 1.3 |
| * @Immutable |
| * @author $Id: 2a5e4b8c63928ffda304dfe523fc06df49c68eae $ |
| */ |
| |
| public class Version implements Comparable<Version> { |
| private final int major; |
| private final int minor; |
| private final int micro; |
| private final String qualifier; |
| private static final String SEPARATOR = "."; |
| private transient String versionString /* default to null */; |
| private transient int hash /* default to 0 */; |
| |
| /** |
| * The empty version "0.0.0". |
| */ |
| public static final Version emptyVersion = new Version(0, 0, 0); |
| |
| /** |
| * Creates a version identifier from the specified numerical components. |
| * |
| * <p> |
| * The qualifier is set to the empty string. |
| * |
| * @param major Major component of the version identifier. |
| * @param minor Minor component of the version identifier. |
| * @param micro Micro component of the version identifier. |
| * @throws IllegalArgumentException If the numerical components are |
| * negative. |
| */ |
| public Version(int major, int minor, int micro) { |
| this(major, minor, micro, null); |
| } |
| |
| /** |
| * Creates a version identifier from the specified components. |
| * |
| * @param major Major component of the version identifier. |
| * @param minor Minor component of the version identifier. |
| * @param micro Micro component of the version identifier. |
| * @param qualifier Qualifier component of the version identifier. If |
| * {@code null} is specified, then the qualifier will be set to the |
| * empty string. |
| * @throws IllegalArgumentException If the numerical components are negative |
| * or the qualifier string is invalid. |
| */ |
| public Version(int major, int minor, int micro, String qualifier) { |
| if (qualifier == null) { |
| qualifier = ""; |
| } |
| |
| this.major = major; |
| this.minor = minor; |
| this.micro = micro; |
| this.qualifier = qualifier; |
| validate(); |
| } |
| |
| /** |
| * Creates a version identifier from the specified string. |
| * |
| * <p> |
| * Version string grammar: |
| * |
| * <pre> |
| * version ::= major('.'minor('.'micro('.'qualifier)?)?)? |
| * major ::= digit+ |
| * minor ::= digit+ |
| * micro ::= digit+ |
| * qualifier ::= (alpha|digit|'_'|'-')+ |
| * digit ::= [0..9] |
| * alpha ::= [a..zA..Z] |
| * </pre> |
| * |
| * @param version String representation of the version identifier. There |
| * must be no whitespace in the argument. |
| * @throws IllegalArgumentException If {@code version} is improperly |
| * formatted. |
| */ |
| public Version(String version) { |
| int maj = 0; |
| int min = 0; |
| int mic = 0; |
| String qual = ""; |
| |
| try { |
| StringTokenizer st = new StringTokenizer(version, SEPARATOR, true); |
| maj = parseInt(st.nextToken(), version); |
| |
| if (st.hasMoreTokens()) { // minor |
| st.nextToken(); // consume delimiter |
| min = parseInt(st.nextToken(), version); |
| |
| if (st.hasMoreTokens()) { // micro |
| st.nextToken(); // consume delimiter |
| mic = parseInt(st.nextToken(), version); |
| |
| if (st.hasMoreTokens()) { // qualifier separator |
| st.nextToken(); // consume delimiter |
| qual = st.nextToken(""); // remaining string |
| |
| if (st.hasMoreTokens()) { // fail safe |
| throw new IllegalArgumentException("invalid version \"" + version + "\": invalid format"); |
| } |
| } |
| } |
| } |
| } catch (NoSuchElementException e) { |
| throw new IllegalArgumentException( |
| "invalid version \"" + version + "\": invalid format", e); |
| } |
| |
| major = maj; |
| minor = min; |
| micro = mic; |
| qualifier = qual; |
| validate(); |
| } |
| |
| /** |
| * Parse numeric component into an int. |
| * |
| * @param value Numeric component |
| * @param version Complete version string for exception message, if any |
| * @return int value of numeric component |
| */ |
| private static int parseInt(String value, String version) { |
| try { |
| return Integer.parseInt(value); |
| } catch (NumberFormatException e) { |
| throw new IllegalArgumentException("invalid version \"" + version |
| + "\": non-numeric \"" + value + "\"", e); |
| } |
| } |
| |
| /** |
| * Called by the Version constructors to validate the version components. |
| * |
| * @throws IllegalArgumentException If the numerical components are negative |
| * or the qualifier string is invalid. |
| */ |
| private void validate() { |
| if (major < 0) { |
| throw new IllegalArgumentException("invalid version \"" + toString0() + "\": negative number \"" + major + "\""); |
| } |
| if (minor < 0) { |
| throw new IllegalArgumentException("invalid version \"" + toString0() + "\": negative number \"" + minor + "\""); |
| } |
| if (micro < 0) { |
| throw new IllegalArgumentException("invalid version \"" + toString0() + "\": negative number \"" + micro + "\""); |
| } |
| for (char ch : qualifier.toCharArray()) { |
| if (('A' <= ch) && (ch <= 'Z')) { |
| continue; |
| } |
| if (('a' <= ch) && (ch <= 'z')) { |
| continue; |
| } |
| if (('0' <= ch) && (ch <= '9')) { |
| continue; |
| } |
| if ((ch == '_') || (ch == '-')) { |
| continue; |
| } |
| throw new IllegalArgumentException("invalid version \"" + toString0() + "\": invalid qualifier \"" + qualifier + "\""); |
| } |
| } |
| |
| /** |
| * Parses a version identifier from the specified string. |
| * |
| * <p> |
| * See {@link #Version(String)} for the format of the version string. |
| * |
| * @param version String representation of the version identifier. Leading |
| * and trailing whitespace will be ignored. |
| * @return A {@code Version} object representing the version identifier. If |
| * {@code version} is {@code null} or the empty string then |
| * {@link #emptyVersion} will be returned. |
| * @throws IllegalArgumentException If {@code version} is improperly |
| * formatted. |
| */ |
| public static Version parseVersion(String version) { |
| if (version == null) { |
| return emptyVersion; |
| } |
| |
| return valueOf(version); |
| } |
| |
| /** |
| * Returns a {@code Version} object holding the version identifier in the |
| * specified {@code String}. |
| * |
| * <p> |
| * See {@link #Version(String)} for the format of the version string. |
| * |
| * <p> |
| * This method performs a similar function as {@link #parseVersion(String)} |
| * but has the static factory {@code valueOf(String)} method signature. |
| * |
| * @param version String representation of the version identifier. Leading |
| * and trailing whitespace will be ignored. Must not be {@code null}. |
| * @return A {@code Version} object representing the version identifier. If |
| * {@code version} is the empty string then {@link #emptyVersion} |
| * will be returned. |
| * @throws IllegalArgumentException If {@code version} is improperly |
| * formatted. |
| * @since 1.8 |
| */ |
| public static Version valueOf(String version) { |
| version = version.trim(); |
| if (version.length() == 0) { |
| return emptyVersion; |
| } |
| |
| return new Version(version); |
| } |
| |
| /** |
| * Returns the major component of this version identifier. |
| * |
| * @return The major component. |
| */ |
| public int getMajor() { |
| return major; |
| } |
| |
| /** |
| * Returns the minor component of this version identifier. |
| * |
| * @return The minor component. |
| */ |
| public int getMinor() { |
| return minor; |
| } |
| |
| /** |
| * Returns the micro component of this version identifier. |
| * |
| * @return The micro component. |
| */ |
| public int getMicro() { |
| return micro; |
| } |
| |
| /** |
| * Returns the qualifier component of this version identifier. |
| * |
| * @return The qualifier component. |
| */ |
| public String getQualifier() { |
| return qualifier; |
| } |
| |
| /** |
| * Returns the string representation of this version identifier. |
| * |
| * <p> |
| * The format of the version string will be {@code major.minor.micro} if |
| * qualifier is the empty string or {@code major.minor.micro.qualifier} |
| * otherwise. |
| * |
| * @return The string representation of this version identifier. |
| */ |
| @Override |
| public String toString() { |
| return toString0(); |
| } |
| |
| /** |
| * Internal toString behavior |
| * |
| * @return The string representation of this version identifier. |
| */ |
| String toString0() { |
| String s = versionString; |
| if (s != null) { |
| return s; |
| } |
| int q = qualifier.length(); |
| StringBuilder result = new StringBuilder(20 + q); |
| result.append(major); |
| result.append(SEPARATOR); |
| result.append(minor); |
| result.append(SEPARATOR); |
| result.append(micro); |
| if (q > 0) { |
| result.append(SEPARATOR); |
| result.append(qualifier); |
| } |
| return versionString = result.toString(); |
| } |
| |
| /** |
| * Returns a hash code value for the object. |
| * |
| * @return An integer which is a hash code value for this object. |
| */ |
| @Override |
| public int hashCode() { |
| int h = hash; |
| if (h != 0) { |
| return h; |
| } |
| h = 31 * 17; |
| h = 31 * h + major; |
| h = 31 * h + minor; |
| h = 31 * h + micro; |
| h = 31 * h + qualifier.hashCode(); |
| return hash = h; |
| } |
| |
| /** |
| * Compares this {@code Version} object to another object. |
| * |
| * <p> |
| * A version is considered to be <b>equal to </b> another version if the |
| * major, minor and micro components are equal and the qualifier component |
| * is equal (using {@code String.equals}). |
| * |
| * @param object The {@code Version} object to be compared. |
| * @return {@code true} if {@code object} is a {@code Version} and is equal |
| * to this object; {@code false} otherwise. |
| */ |
| @Override |
| public boolean equals(Object object) { |
| if (object == this) { // quicktest |
| return true; |
| } |
| |
| if (!(object instanceof Version)) { |
| return false; |
| } |
| |
| Version other = (Version) object; |
| return (major == other.major) && (minor == other.minor) && (micro == other.micro) && qualifier.equals(other.qualifier); |
| } |
| |
| /** |
| * Compares this {@code Version} object to another {@code Version}. |
| * |
| * <p> |
| * A version is considered to be <b>less than</b> another version if its |
| * major component is less than the other version's major component, or the |
| * major components are equal and its minor component is less than the other |
| * version's minor component, or the major and minor components are equal |
| * and its micro component is less than the other version's micro component, |
| * or the major, minor and micro components are equal and it's qualifier |
| * component is less than the other version's qualifier component (using |
| * {@code String.compareTo}). |
| * |
| * <p> |
| * A version is considered to be <b>equal to</b> another version if the |
| * major, minor and micro components are equal and the qualifier component |
| * is equal (using {@code String.compareTo}). |
| * |
| * @param other The {@code Version} object to be compared. |
| * @return A negative integer, zero, or a positive integer if this version |
| * is less than, equal to, or greater than the specified |
| * {@code Version} object. |
| * @throws ClassCastException If the specified object is not a |
| * {@code Version} object. |
| */ |
| @Override |
| public int compareTo(Version other) { |
| if (other == this) { // quicktest |
| return 0; |
| } |
| |
| int result = major - other.major; |
| if (result != 0) { |
| return result; |
| } |
| |
| result = minor - other.minor; |
| if (result != 0) { |
| return result; |
| } |
| |
| result = micro - other.micro; |
| if (result != 0) { |
| return result; |
| } |
| |
| return qualifier.compareTo(other.qualifier); |
| } |
| } |