/* * 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 */ public VersionRange( Version atLeast ) { this( atLeast, false ); } /** * atLeast constructor * * @param atLeast */ 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 * @return */ 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 ); } }