| /* |
| * 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.felix.utils.version; |
| |
| import java.io.Serializable; |
| |
| import org.osgi.framework.Version; |
| |
| public class VersionRange implements Serializable |
| { |
| |
| /** |
| * |
| */ |
| private static final long serialVersionUID = 1L; |
| |
| public static final Version INFINITE_VERSION = new Version( Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, "" ); |
| public static final VersionRange ANY_VERSION = new VersionRange( false, Version.emptyVersion, INFINITE_VERSION, true ); |
| |
| public static final int EXACT = 0; |
| public static final int MICRO = 1; |
| public static final int MINOR = 2; |
| public static final int MAJOR = 3; |
| public static final int ANY = 40; |
| |
| private final boolean openFloor; |
| private final Version floor; |
| private final Version ceiling; |
| private final boolean openCeiling; |
| |
| |
| /** |
| * Interval constructor |
| * |
| * @param openFloor Whether the lower bound of the range is inclusive (false) or exclusive (true). |
| * @param floor The lower bound version of the range. |
| * @param ceiling The upper bound version of the range. |
| * @param openCeiling Whether the upper bound of the range is inclusive (false) or exclusive (true). |
| */ |
| public VersionRange( boolean openFloor, Version floor, Version ceiling, boolean openCeiling ) |
| { |
| this.openFloor = openFloor; |
| this.floor = floor; |
| this.ceiling = ceiling; |
| this.openCeiling = openCeiling; |
| checkRange(); |
| } |
| |
| |
| /** |
| * atLeast constructor |
| * |
| * @param atLeast Minimum version |
| */ |
| public VersionRange( Version atLeast ) |
| { |
| this( atLeast, false ); |
| } |
| |
| /** |
| * atLeast constructor |
| * |
| * @param atLeast Minimum version |
| * @param exact Exact range |
| */ |
| public VersionRange( Version atLeast, boolean exact ) |
| { |
| |
| this.openFloor = false; |
| this.floor = atLeast; |
| this.ceiling = exact ? atLeast : INFINITE_VERSION; |
| this.openCeiling = exact ? false : true; |
| checkRange(); |
| } |
| |
| |
| public VersionRange( String val ) throws IllegalArgumentException, NumberFormatException |
| { |
| this( val, false ); |
| } |
| |
| public VersionRange( String val, boolean exact ) throws IllegalArgumentException, NumberFormatException |
| { |
| this( val, exact, true ); |
| } |
| |
| public VersionRange( String val, boolean exact, boolean clean ) throws IllegalArgumentException, NumberFormatException |
| { |
| val = removeQuotesAndWhitespaces(val); |
| int fst = val.charAt( 0 ); |
| if ( fst == '[' ) |
| { |
| openFloor = false; |
| } |
| else if ( fst == '(' ) |
| { |
| openFloor = true; |
| } |
| else |
| { |
| openFloor = false; |
| floor = VersionTable.getVersion( val, clean ); |
| ceiling = exact ? floor : INFINITE_VERSION; |
| openCeiling = exact ? false : true; |
| return; |
| } |
| |
| int lst = val.charAt( val.length() - 1 ); |
| if ( lst == ']' ) |
| { |
| openCeiling = false; |
| } |
| else if ( lst == ')' ) |
| { |
| openCeiling = true; |
| } |
| else |
| { |
| throw new IllegalArgumentException( "illegal version range syntax " + val |
| + ": range must end in ')' or ']'" ); |
| } |
| |
| int comma = val.indexOf( ',' ); |
| if ( comma < 0 ) |
| { |
| throw new IllegalArgumentException( "illegal version range syntax " + "no comma" ); |
| } |
| if ( val.indexOf( ',', comma + 1 ) > 0 ) |
| { |
| throw new IllegalArgumentException( "illegal version range syntax " + "too many commas" ); |
| } |
| String strFloor = val.substring( 1, comma ); |
| String strCeil = val.substring( comma + 1, val.length() - 1 ); |
| floor = VersionTable.getVersion( strFloor, clean ); |
| ceiling = "*".equals( strCeil ) ? INFINITE_VERSION : VersionTable.getVersion( strCeil, clean ); |
| checkRange(); |
| } |
| |
| private String removeQuotesAndWhitespaces(String val) { |
| for (int i = 0, l = val.length(); i < l; i++) { |
| char ch = val.charAt(i); |
| if (isRemoveable(ch)) { |
| StringBuilder sb = new StringBuilder(l); |
| sb.append(val, 0, i); |
| for (i++; i < l; i++) { |
| ch = val.charAt(i); |
| if (!isRemoveable(ch)) { |
| sb.append(ch); |
| } |
| } |
| return sb.toString(); |
| } |
| } |
| return val; |
| } |
| |
| private static boolean[] removeable; |
| static { |
| removeable = new boolean[256]; |
| for (int i = 0; i < 256; i++) { |
| removeable[i] = Character.isWhitespace(i); |
| } |
| removeable['"'] = true; |
| } |
| |
| private boolean isRemoveable(char ch) { |
| return ch < 256 ? removeable[ch] : Character.isWhitespace(ch); |
| } |
| |
| public static VersionRange parseVersionRange( String val ) throws IllegalArgumentException, NumberFormatException |
| { |
| if ( val == null || val.trim().length() == 0 ) |
| { |
| return ANY_VERSION; |
| } |
| |
| return new VersionRange( val ); |
| } |
| |
| |
| public Version getCeiling() |
| { |
| return ceiling; |
| } |
| |
| |
| public Version getFloor() |
| { |
| return floor; |
| } |
| |
| |
| public boolean isOpenCeiling() |
| { |
| return openCeiling; |
| } |
| |
| |
| public boolean isOpenFloor() |
| { |
| return openFloor; |
| } |
| |
| |
| public boolean isPointVersion() |
| { |
| return !openFloor && !openCeiling && floor.equals( ceiling ); |
| } |
| |
| |
| /** |
| * test a version to see if it falls in the range |
| * |
| * @param version The version to check |
| * @return Whether the version is within the range |
| */ |
| public boolean contains( Version version ) |
| { |
| if ( version.equals( INFINITE_VERSION ) ) |
| { |
| return ceiling.equals( INFINITE_VERSION ); |
| } |
| else |
| { |
| return ( version.compareTo( floor ) > 0 && version.compareTo( ceiling ) < 0 ) |
| || ( !openFloor && version.equals( floor ) ) || ( !openCeiling && version.equals( ceiling ) ); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.apache.aries.application.impl.VersionRange#intersect(VersionRange |
| * range) |
| */ |
| |
| public VersionRange intersect(VersionRange r) |
| { |
| // Use the highest minimum version. |
| final Version newFloor; |
| final boolean newOpenFloor; |
| int minCompare = floor.compareTo(r.getFloor()); |
| if (minCompare > 0) |
| { |
| newFloor = floor; |
| newOpenFloor = openFloor; |
| } |
| else if (minCompare < 0) |
| { |
| newFloor = r.getFloor(); |
| newOpenFloor = r.isOpenFloor(); |
| } |
| else |
| { |
| newFloor = floor; |
| newOpenFloor = (openFloor || r.isOpenFloor()); |
| } |
| |
| // Use the lowest maximum version. |
| final Version newCeiling; |
| final boolean newOpenCeiling; |
| // null maximum version means unbounded, so the highest possible value. |
| int maxCompare = ceiling.compareTo(r.getCeiling()); |
| if (maxCompare < 0) |
| { |
| newCeiling = ceiling; |
| newOpenCeiling = openCeiling; |
| } |
| else if (maxCompare > 0) |
| { |
| newCeiling = r.getCeiling(); |
| newOpenCeiling = r.isOpenCeiling(); |
| } |
| else |
| { |
| newCeiling = ceiling; |
| newOpenCeiling = (openCeiling || r.isOpenCeiling()); |
| } |
| |
| VersionRange result; |
| if (isRangeValid(newOpenFloor, newFloor, newCeiling, newOpenCeiling)) |
| { |
| result = new VersionRange(newOpenFloor, newFloor, newCeiling, newOpenCeiling); |
| } |
| else |
| { |
| result = null; |
| } |
| return result; |
| } |
| |
| /** |
| * Check if the supplied parameters describe a valid version range. |
| * |
| * @param floor |
| * the minimum version. |
| * @param openFloor |
| * whether the minimum version is exclusive. |
| * @param ceiling |
| * the maximum version. |
| * @param openCeiling |
| * whether the maximum version is exclusive. |
| * @return true is the range is valid; otherwise false. |
| */ |
| private static boolean isRangeValid(boolean openFloor, Version floor, Version ceiling, boolean openCeiling) { |
| boolean result; |
| int compare = floor.compareTo(ceiling); |
| if (compare > 0) |
| { |
| // Minimum larger than maximum is invalid. |
| result = false; |
| } |
| else if (compare == 0 && (openFloor || openCeiling)) |
| { |
| // If floor and ceiling are the same, and either are exclusive, no valid range |
| // exists. |
| result = false; |
| } |
| else |
| { |
| // Range is valid. |
| result = true; |
| } |
| return result; |
| } |
| |
| private void checkRange() |
| { |
| if (!isRangeValid(openFloor, floor, ceiling, openCeiling)) |
| { |
| throw new IllegalArgumentException("invalid version range: " + makeString(openFloor, floor, ceiling, openCeiling)); |
| } |
| } |
| |
| |
| public int hashCode() |
| { |
| final int prime = 31; |
| int result = 1; |
| result = prime * result + ( ( ceiling == null ) ? 0 : ceiling.hashCode() ); |
| result = prime * result + ( ( floor == null ) ? 0 : floor.hashCode() ); |
| result = prime * result + ( openCeiling ? 1231 : 1237 ); |
| result = prime * result + ( openFloor ? 1231 : 1237 ); |
| return result; |
| } |
| |
| |
| public boolean equals( Object obj ) |
| { |
| if ( this == obj ) |
| return true; |
| if ( obj == null ) |
| return false; |
| if ( getClass() != obj.getClass() ) |
| return false; |
| final VersionRange other = ( VersionRange ) obj; |
| if ( ceiling == null ) |
| { |
| if ( other.ceiling != null ) |
| return false; |
| } |
| else if ( !ceiling.equals( other.ceiling ) ) |
| return false; |
| if ( floor == null ) |
| { |
| if ( other.floor != null ) |
| return false; |
| } |
| else if ( !floor.equals( other.floor ) ) |
| return false; |
| if ( openCeiling != other.openCeiling ) |
| return false; |
| if ( openFloor != other.openFloor ) |
| return false; |
| return true; |
| } |
| |
| |
| public String toString() |
| { |
| if ( ANY_VERSION.equals( this ) ) |
| { |
| return makeString( openFloor, Version.emptyVersion, INFINITE_VERSION, openCeiling ); |
| } |
| return makeString( openFloor, floor, ceiling, openCeiling ); |
| } |
| |
| |
| private String makeString( boolean openFloor, Version floor, Version ceiling, boolean openCeiling ) |
| { |
| StringBuffer vr = new StringBuffer( 32 ); |
| if ( INFINITE_VERSION.equals( ceiling ) ) |
| { |
| vr.append( Version.emptyVersion.equals( floor ) ? "0" : floor.toString() ); |
| } |
| else |
| { |
| vr.append( openFloor ? "(" : "[" ); |
| String floorStr = Version.emptyVersion.equals( floor ) ? "0" : floor.toString(); |
| String ceilingStr = ceiling.toString(); |
| vr.append( floorStr ).append( "," ).append( ceilingStr ); |
| vr.append( openCeiling ? ")" : "]" ); |
| } |
| return vr.toString(); |
| } |
| |
| |
| public static VersionRange newInstance( Version pointVersion, |
| int lowerBoundRule, |
| int upperBoundRule ) |
| { |
| Version floor = null; |
| switch ( lowerBoundRule ) |
| { |
| case ANY: |
| floor = VersionTable.getVersion( 0, 0, 0 ); |
| break; |
| case MAJOR: |
| floor = VersionTable.getVersion( pointVersion.getMajor(), 0, 0 ); |
| break; |
| case MINOR: |
| floor = VersionTable.getVersion( pointVersion.getMajor(), pointVersion.getMinor(), 0 ); |
| break; |
| case MICRO: |
| floor = VersionTable.getVersion( pointVersion.getMajor(), pointVersion.getMinor(), pointVersion.getMicro() ); |
| break; |
| case EXACT: |
| floor = pointVersion; |
| break; |
| } |
| |
| Version ceiling = null; |
| boolean openCeiling = true; |
| switch ( upperBoundRule ) |
| { |
| case ANY: |
| ceiling = INFINITE_VERSION; |
| break; |
| case MAJOR: |
| ceiling = VersionTable.getVersion( pointVersion.getMajor() + 1, 0, 0 ); |
| break; |
| case MINOR: |
| ceiling = VersionTable.getVersion( pointVersion.getMajor(), pointVersion.getMinor() + 1, 0 ); |
| break; |
| case MICRO: |
| ceiling = VersionTable.getVersion( pointVersion.getMajor(), pointVersion.getMinor(), pointVersion.getMicro() + 1 ); |
| break; |
| case EXACT: |
| ceiling = pointVersion; |
| openCeiling = false; |
| break; |
| } |
| |
| return new VersionRange( false, floor, ceiling, openCeiling ); |
| } |
| } |