/*
* Copyright to the original author or authors.
*
* 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.rioproject.version;
import java.io.Serializable;
/**
* The {@code VersionMatcher} is a utility that determines whether a required version can be met by a published version.
*
* </br>
* Version matching is done as follows: <br>
* <br>
* <table cellpadding="2" cellspacing="2" border="1"
* style="text-align: left; width: 100%;">
* <tbody>
* <tr>
* <th style="vertical-align: top;">Requirement<br>
* </th>
* <th style="vertical-align: top;">Support Criteria<br>
* </th>
* </tr>
* <tr>
* <td style="vertical-align: top;">1.2.7<br>
* </td>
* <td style="vertical-align: top;">Specifies an exact version<br>
* </td>
* </tr>
* <tr>
* <td style="vertical-align: top;">2*<br>
* </td>
* <td style="vertical-align: top;">Supported for all minor versions of 2 <br>
* </td>
* </tr>
* <tr>
* <td style="vertical-align: top;">3.4*<br>
* </td>
* <td style="vertical-align: top;">Supported for all minor versions of 3.4, including 3.4<br>
* </td>
* </tr>
* <tr>
* <td style="vertical-align: top;">1.2+<br>
* </td>
* <td style="vertical-align: top;">Supported for version 1.2 or above</td>
* </tr>
* </tbody>
* </table>
* </br>
*
* <p>
* Version requirements are expected to be a "." or "-" separated String of integers. Character values are ignored.
* For example a version declaration of "2.0-M3" will be processed as "2.0-03"
* </p>
* @author Dennis Reedy
*/
public class VersionMatcher implements Serializable {
private static final long serialVersionUID = 1l;
/**
* Determine if versions are supported
*
* @param requiredVersion The required version, specified as a string. Must not be {@code null}.
* @param publishedVersion The published version value. Must not be {@code null}.
*
* @return {@code true} if, and only if, the request version can be supported
*
* @throws IllegalArgumentException if either of the parameters are {@code null}.
*/
public boolean versionSupported(final String requiredVersion, final String publishedVersion) {
if(requiredVersion == null)
throw new IllegalArgumentException("requiredVersion is null");
if(publishedVersion == null)
throw new IllegalArgumentException("publishedVersion is null");
boolean supported;
Version versionRequired = new Version(requiredVersion);
Version versionBeingTested = new Version(publishedVersion);
if(versionRequired.minorVersionSupport() || versionRequired.majorVersionSupport()) {
boolean minorVersionSupport = versionRequired.minorVersionSupport();
int[] required;
int[] configured;
boolean isRange = versionBeingTested.isRange();
try {
required = toIntArray(versionRequired.getVersion().split("\\D"));
configured = toIntArray(versionBeingTested.getVersion().split("\\D"));
} catch(NumberFormatException e) {
return (false);
}
if(required.length == 0)
return (true);
if(minorVersionSupport) {
supported = checkMinorVersion(required, configured);
} else {
supported = checkMajorVersion(required, configured);
}
if(!supported && isRange) {
try {
configured = toIntArray(versionBeingTested.getEndRange().split("\\D"));
} catch(NumberFormatException e) {
return (false);
}
if(minorVersionSupport) {
supported = checkMinorVersion(required, configured);
} else {
supported = checkMajorVersion(required, configured);
}
}
} else {
if(versionBeingTested.isRange()) {
supported = withinRange(versionBeingTested, versionRequired);
} else {
supported = versionBeingTested.equals(versionRequired);
}
}
return supported;
}
private boolean withinRange(Version publishedVersion, Version requiredVersion) {
int[] startRange = toIntArray(publishedVersion.getStartRange().split("\\D"));
int[] endRange = toIntArray(publishedVersion.getEndRange().split("\\D"));
int[] version = toIntArray(requiredVersion.getVersion().split("\\D"));
boolean startRangePassed = false;
boolean same = false;
for(int i=0;;i++) {
if(i>=startRange.length) {
startRangePassed = true;
break;
}
if(i>=version.length) {
startRangePassed = !same;
break;
}
if(version[i]>startRange[i]) {
startRangePassed = true;
break;
}
if(version[i]<startRange[i]) {
break;
}
same = version[i]==startRange[i];
}
boolean endRangePassed = false;
if(startRangePassed) {
same = false;
for(int i=0;;i++) {
if(i>=endRange.length) {
endRangePassed = !(i<version.length && same);
break;
}
if(version[i]<endRange[i]) {
endRangePassed = true;
break;
}
if(version[i]>endRange[i]) {
break;
}
same = version[i]==endRange[i];
}
}
return startRangePassed&&endRangePassed;
}
private boolean checkMinorVersion(int[] required, int[] configured) {
boolean supported = true;
for(int i = 0; i < required.length; i++) {
if(configured.length >= (i+1)) {
if(configured[i] != required[i]) {
supported = false;
break;
}
}
}
return supported;
}
private boolean checkMajorVersion(int[] required, int[] configured) {
boolean supported = true;
for(int i = 0; i < required.length; i++) {
if(configured.length >= (i+1)) {
if(configured[i] > required[i]) {
break;
}
if(configured[i] == required[i]) {
continue;
}
if(configured[i] < required[i]) {
supported = false;
break;
}
}
}
return supported;
}
/**
* Convert a String array into an int array
* @param a String array
*
* @return An int array
*
* @throws NumberFormatException if the value in the array is not a number
*/
private int[] toIntArray(final String[] a) throws NumberFormatException {
int[] array = new int[a.length];
for(int i = 0; i < array.length; i++) {
if(a[i].length() == 0) {
array[i] = Integer.parseInt("0");
} else {
array[i] = Integer.parseInt(a[i]);
}
}
return (array);
}
}