/*
 * Copyright (c) OSGi Alliance (2011, 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 range. A version range is an interval describing a set of versions.

A range has a left (lower) endpoint and a right (upper) endpoint. Each endpoint can be open (excluded from the set) or closed (included in the set).

VersionRange objects are immutable.

Author:$Id: cc407ff6fb1d5252b61a033924e63b751880f580 $
Since:1.7
@Immutable
/** * Version range. A version range is an interval describing a set of * {@link Version versions}. * * <p> * A range has a left (lower) endpoint and a right (upper) endpoint. Each * endpoint can be open (excluded from the set) or closed (included in the set). * * <p> * {@code VersionRange} objects are immutable. * * @since 1.7 * @Immutable * @author $Id: cc407ff6fb1d5252b61a033924e63b751880f580 $ */
public class VersionRange {
The left endpoint is open and is excluded from the range.

The value of LEFT_OPEN is '('.

/** * The left endpoint is open and is excluded from the range. * <p> * The value of {@code LEFT_OPEN} is {@code '('}. */
public static final char LEFT_OPEN = '(';
The left endpoint is closed and is included in the range.

The value of LEFT_CLOSED is '['.

/** * The left endpoint is closed and is included in the range. * <p> * The value of {@code LEFT_CLOSED} is {@code '['}. */
public static final char LEFT_CLOSED = '[';
The right endpoint is open and is excluded from the range.

The value of RIGHT_OPEN is ')'.

/** * The right endpoint is open and is excluded from the range. * <p> * The value of {@code RIGHT_OPEN} is {@code ')'}. */
public static final char RIGHT_OPEN = ')';
The right endpoint is closed and is included in the range.

The value of RIGHT_CLOSED is ']'.

/** * The right endpoint is closed and is included in the range. * <p> * The value of {@code RIGHT_CLOSED} is {@code ']'}. */
public static final char RIGHT_CLOSED = ']'; private final boolean leftClosed; private final Version left; private final Version right; private final boolean rightClosed; private final boolean empty; private transient String versionRangeString /* default to null */; private transient int hash /* default to 0 */; private static final String LEFT_OPEN_DELIMITER = "("; private static final String LEFT_CLOSED_DELIMITER = "["; private static final String LEFT_DELIMITERS = LEFT_CLOSED_DELIMITER + LEFT_OPEN_DELIMITER; private static final String RIGHT_OPEN_DELIMITER = ")"; private static final String RIGHT_CLOSED_DELIMITER = "]"; private static final String RIGHT_DELIMITERS = RIGHT_OPEN_DELIMITER + RIGHT_CLOSED_DELIMITER; private static final String ENDPOINT_DELIMITER = ",";
Creates a version range from the specified versions.
Params:
  • leftType – Must be either LEFT_CLOSED or LEFT_OPEN .
  • leftEndpoint – Left endpoint of range. Must not be null.
  • rightEndpoint – Right endpoint of range. May be null to indicate the right endpoint is Infinity.
  • rightType – Must be either RIGHT_CLOSED or RIGHT_OPEN.
Throws:
/** * Creates a version range from the specified versions. * * @param leftType Must be either {@link #LEFT_CLOSED} or {@link #LEFT_OPEN} * . * @param leftEndpoint Left endpoint of range. Must not be {@code null}. * @param rightEndpoint Right endpoint of range. May be {@code null} to * indicate the right endpoint is <i>Infinity</i>. * @param rightType Must be either {@link #RIGHT_CLOSED} or * {@link #RIGHT_OPEN}. * @throws IllegalArgumentException If the arguments are invalid. */
public VersionRange(char leftType, Version leftEndpoint, Version rightEndpoint, char rightType) { if ((leftType != LEFT_CLOSED) && (leftType != LEFT_OPEN)) { throw new IllegalArgumentException("invalid leftType \"" + leftType + "\""); } if ((rightType != RIGHT_OPEN) && (rightType != RIGHT_CLOSED)) { throw new IllegalArgumentException("invalid rightType \"" + rightType + "\""); } if (leftEndpoint == null) { throw new IllegalArgumentException("null leftEndpoint argument"); } leftClosed = leftType == LEFT_CLOSED; rightClosed = rightType == RIGHT_CLOSED; left = leftEndpoint; right = rightEndpoint; empty = isEmpty0(); }
Creates a version range from the specified string.

Version range string grammar:

range ::= interval | atleast
interval ::= ( '[' | '(' ) left ',' right ( ']' | ')' )
left ::= version
right ::= version
atleast ::= version
Params:
  • range – String representation of the version range. The versions in the range must contain no whitespace. Other whitespace in the range string is ignored. Must not be null.
Throws:
/** * Creates a version range from the specified string. * * <p> * Version range string grammar: * * <pre> * range ::= interval | atleast * interval ::= ( '[' | '(' ) left ',' right ( ']' | ')' ) * left ::= version * right ::= version * atleast ::= version * </pre> * * @param range String representation of the version range. The versions in * the range must contain no whitespace. Other whitespace in the * range string is ignored. Must not be {@code null}. * @throws IllegalArgumentException If {@code range} is improperly * formatted. */
public VersionRange(String range) { boolean closedLeft; boolean closedRight; Version endpointLeft; Version endpointRight; try { StringTokenizer st = new StringTokenizer(range, LEFT_DELIMITERS, true); String token = st.nextToken().trim(); // whitespace or left delim if (token.length() == 0) { // leading whitespace token = st.nextToken(); // left delim } closedLeft = LEFT_CLOSED_DELIMITER.equals(token); if (!closedLeft && !LEFT_OPEN_DELIMITER.equals(token)) { // first token is not a delimiter, so it must be "atleast" if (st.hasMoreTokens()) { // there must be no more tokens throw new IllegalArgumentException("invalid range \"" + range + "\": invalid format"); } leftClosed = true; rightClosed = false; left = parseVersion(token, range); right = null; empty = false; return; } String version = st.nextToken(ENDPOINT_DELIMITER); endpointLeft = parseVersion(version, range); token = st.nextToken(); // consume comma version = st.nextToken(RIGHT_DELIMITERS); token = st.nextToken(); // right delim closedRight = RIGHT_CLOSED_DELIMITER.equals(token); if (!closedRight && !RIGHT_OPEN_DELIMITER.equals(token)) { throw new IllegalArgumentException("invalid range \"" + range + "\": invalid format"); } endpointRight = parseVersion(version, range); if (st.hasMoreTokens()) { // any more tokens have to be whitespace token = st.nextToken("").trim(); if (token.length() != 0) { // trailing whitespace throw new IllegalArgumentException("invalid range \"" + range + "\": invalid format"); } } } catch (NoSuchElementException e) { throw new IllegalArgumentException( "invalid range \"" + range + "\": invalid format", e); } leftClosed = closedLeft; rightClosed = closedRight; left = endpointLeft; right = endpointRight; empty = isEmpty0(); }
Parse version component into a Version.
Params:
  • version – version component string
  • range – Complete range string for exception message, if any
Returns:Version
/** * Parse version component into a Version. * * @param version version component string * @param range Complete range string for exception message, if any * @return Version */
private static Version parseVersion(String version, String range) { try { return Version.valueOf(version); } catch (IllegalArgumentException e) { throw new IllegalArgumentException( "invalid range \"" + range + "\": " + e.getMessage(), e); } }
Returns the left endpoint of this version range.
Returns:The left endpoint.
/** * Returns the left endpoint of this version range. * * @return The left endpoint. */
public Version getLeft() { return left; }
Returns the right endpoint of this version range.
Returns:The right endpoint. May be null which indicates the right endpoint is Infinity.
/** * Returns the right endpoint of this version range. * * @return The right endpoint. May be {@code null} which indicates the right * endpoint is <i>Infinity</i>. */
public Version getRight() { return right; }
Returns the type of the left endpoint of this version range.
Returns:LEFT_CLOSED if the left endpoint is closed or LEFT_OPEN if the left endpoint is open.
/** * Returns the type of the left endpoint of this version range. * * @return {@link #LEFT_CLOSED} if the left endpoint is closed or * {@link #LEFT_OPEN} if the left endpoint is open. */
public char getLeftType() { return leftClosed ? LEFT_CLOSED : LEFT_OPEN; }
Returns the type of the right endpoint of this version range.
Returns:RIGHT_CLOSED if the right endpoint is closed or RIGHT_OPEN if the right endpoint is open.
/** * Returns the type of the right endpoint of this version range. * * @return {@link #RIGHT_CLOSED} if the right endpoint is closed or * {@link #RIGHT_OPEN} if the right endpoint is open. */
public char getRightType() { return rightClosed ? RIGHT_CLOSED : RIGHT_OPEN; }
Returns whether this version range includes the specified version.
Params:
  • version – The version to test for inclusion in this version range.
Returns:true if the specified version is included in this version range; false otherwise.
/** * Returns whether this version range includes the specified version. * * @param version The version to test for inclusion in this version range. * @return {@code true} if the specified version is included in this version * range; {@code false} otherwise. */
public boolean includes(Version version) { if (empty) { return false; } if (left.compareTo(version) >= (leftClosed ? 1 : 0)) { return false; } if (right == null) { return true; } return right.compareTo(version) >= (rightClosed ? 0 : 1); }
Returns the intersection of this version range with the specified version ranges.
Params:
  • ranges – The version ranges to intersect with this version range.
Returns:A version range representing the intersection of this version range and the specified version ranges. If no version ranges are specified, then this version range is returned.
/** * Returns the intersection of this version range with the specified version * ranges. * * @param ranges The version ranges to intersect with this version range. * @return A version range representing the intersection of this version * range and the specified version ranges. If no version ranges are * specified, then this version range is returned. */
public VersionRange intersection(VersionRange... ranges) { if ((ranges == null) || (ranges.length == 0)) { return this; } // prime with data from this version range boolean closedLeft = leftClosed; boolean closedRight = rightClosed; Version endpointLeft = left; Version endpointRight = right; for (VersionRange range : ranges) { int comparison = endpointLeft.compareTo(range.left); if (comparison == 0) { closedLeft = closedLeft && range.leftClosed; } else { if (comparison < 0) { // move endpointLeft to the right endpointLeft = range.left; closedLeft = range.leftClosed; } } if (range.right != null) { if (endpointRight == null) { endpointRight = range.right; closedRight = range.rightClosed; } else { comparison = endpointRight.compareTo(range.right); if (comparison == 0) { closedRight = closedRight && range.rightClosed; } else { if (comparison > 0) { // move endpointRight to the left endpointRight = range.right; closedRight = range.rightClosed; } } } } } return new VersionRange(closedLeft ? LEFT_CLOSED : LEFT_OPEN, endpointLeft, endpointRight, closedRight ? RIGHT_CLOSED : RIGHT_OPEN); }
Returns whether this version range is empty. A version range is empty if the set of versions defined by the interval is empty.
Returns:true if this version range is empty; false otherwise.
/** * Returns whether this version range is empty. A version range is empty if * the set of versions defined by the interval is empty. * * @return {@code true} if this version range is empty; {@code false} * otherwise. */
public boolean isEmpty() { return empty; }
Internal isEmpty behavior.
Returns:true if this version range is empty; false otherwise.
/** * Internal isEmpty behavior. * * @return {@code true} if this version range is empty; {@code false} * otherwise. */
private boolean isEmpty0() { if (right == null) { // infinity return false; } int comparison = left.compareTo(right); if (comparison == 0) { // endpoints equal return !leftClosed || !rightClosed; } return comparison > 0; // true if left > right }
Returns whether this version range contains only a single version.
Returns:true if this version range contains only a single version; false otherwise.
/** * Returns whether this version range contains only a single version. * * @return {@code true} if this version range contains only a single * version; {@code false} otherwise. */
public boolean isExact() { if (empty || (right == null)) { return false; } if (leftClosed) { if (rightClosed) { // [l,r]: exact if l == r return left.equals(right); } else { // [l,r): exact if l++ >= r Version adjacent1 = new Version(left.getMajor(), left.getMinor(), left.getMicro(), left.getQualifier() + "-"); return adjacent1.compareTo(right) >= 0; } } else { if (rightClosed) { // (l,r] is equivalent to [l++,r]: exact if l++ == r Version adjacent1 = new Version(left.getMajor(), left.getMinor(), left.getMicro(), left.getQualifier() + "-"); return adjacent1.equals(right); } else { // (l,r) is equivalent to [l++,r): exact if (l++)++ >=r Version adjacent2 = new Version(left.getMajor(), left.getMinor(), left.getMicro(), left.getQualifier() + "--"); return adjacent2.compareTo(right) >= 0; } } }
Returns the string representation of this version range.

The format of the version range string will be a version string if the right end point is Infinity (null) or an interval string.

Returns:The string representation of this version range.
/** * Returns the string representation of this version range. * * <p> * The format of the version range string will be a version string if the * right end point is <i>Infinity</i> ({@code null}) or an interval string. * * @return The string representation of this version range. */
@Override public String toString() { String s = versionRangeString; if (s != null) { return s; } String leftVersion = left.toString(); if (right == null) { StringBuilder result = new StringBuilder(leftVersion.length() + 1); result.append(left.toString0()); return versionRangeString = result.toString(); } String rightVerion = right.toString(); StringBuilder result = new StringBuilder( leftVersion.length() + rightVerion.length() + 5); result.append(leftClosed ? LEFT_CLOSED : LEFT_OPEN); result.append(left.toString0()); result.append(ENDPOINT_DELIMITER); result.append(right.toString0()); result.append(rightClosed ? RIGHT_CLOSED : RIGHT_OPEN); return versionRangeString = result.toString(); }
Returns a hash code value for the object.
Returns:An integer which is a hash code value for this object.
/** * 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; } if (empty) { return hash = 31; } h = 31 + (leftClosed ? 7 : 5); h = 31 * h + left.hashCode(); if (right != null) { h = 31 * h + right.hashCode(); h = 31 * h + (rightClosed ? 7 : 5); } return hash = h; }
Compares this VersionRange object to another object.

A version range is considered to be equal to another version range if both the endpoints and their types are equal or if both version ranges are empty.

Params:
  • object – The VersionRange object to be compared.
Returns:true if object is a VersionRange and is equal to this object; false otherwise.
/** * Compares this {@code VersionRange} object to another object. * * <p> * A version range is considered to be <b>equal to </b> another version * range if both the endpoints and their types are equal or if both version * ranges are {@link #isEmpty() empty}. * * @param object The {@code VersionRange} object to be compared. * @return {@code true} if {@code object} is a {@code VersionRange} and is * equal to this object; {@code false} otherwise. */
@Override public boolean equals(Object object) { if (object == this) { // quicktest return true; } if (!(object instanceof VersionRange)) { return false; } VersionRange other = (VersionRange) object; if (empty && other.empty) { return true; } if (right == null) { return (leftClosed == other.leftClosed) && (other.right == null) && left.equals(other.left); } return (leftClosed == other.leftClosed) && (rightClosed == other.rightClosed) && left.equals(other.left) && right.equals(other.right); }
Returns the filter string for this version range using the specified attribute name.
Params:
  • attributeName – The attribute name to use in the returned filter string.
Throws:
See Also:
  • Core Specification, Filters, for a description of the filter string syntax.
Returns:A filter string for this version range using the specified attribute name.
/** * Returns the filter string for this version range using the specified * attribute name. * * @param attributeName The attribute name to use in the returned filter * string. * @return A filter string for this version range using the specified * attribute name. * @throws IllegalArgumentException If the specified attribute name is not a * valid attribute name. * * @see "Core Specification, Filters, for a description of the filter string syntax." */
public String toFilterString(String attributeName) { if (attributeName.length() == 0) { throw new IllegalArgumentException("invalid attributeName \"" + attributeName + "\""); } for (char ch : attributeName.toCharArray()) { if ((ch == '=') || (ch == '>') || (ch == '<') || (ch == '~') || (ch == '(') || (ch == ')')) { throw new IllegalArgumentException("invalid attributeName \"" + attributeName + "\""); } } StringBuilder result = new StringBuilder(128); final boolean needPresence = !leftClosed && ((right == null) || !rightClosed); final boolean multipleTerms = needPresence || (right != null); if (multipleTerms) { result.append("(&"); } if (needPresence) { result.append('('); result.append(attributeName); result.append("=*)"); } if (leftClosed) { result.append('('); result.append(attributeName); result.append(">="); result.append(left.toString0()); result.append(')'); } else { result.append("(!("); result.append(attributeName); result.append("<="); result.append(left.toString0()); result.append("))"); } if (right != null) { if (rightClosed) { result.append('('); result.append(attributeName); result.append("<="); result.append(right.toString0()); result.append(')'); } else { result.append("(!("); result.append(attributeName); result.append(">="); result.append(right.toString0()); result.append("))"); } } if (multipleTerms) { result.append(')'); } return result.toString(); }
Returns a VersionRange object holding the version range in the specified String.

See VersionRange(String) for the format of the version range string.

Params:
  • range – String representation of the version range. The versions in the range must contain no whitespace. Other whitespace in the range string is ignored. Must not be null.
Throws:
Returns:A VersionRange object representing the version range.
Since:1.8
/** * Returns a {@code VersionRange} object holding the version range in the * specified {@code String}. * * <p> * See {@link #VersionRange(String)} for the format of the version range * string. * * @param range String representation of the version range. The versions in * the range must contain no whitespace. Other whitespace in the * range string is ignored. Must not be {@code null}. * @return A {@code VersionRange} object representing the version range. * @throws IllegalArgumentException If {@code range} is improperly * formatted. * @since 1.8 */
public static VersionRange valueOf(String range) { return new VersionRange(range); } }