blob: a9072df1b388a49ddd96edb1d6eaa60694b5d229 [file] [log] [blame]
/*
* 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
*
* https://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.ivy.plugins.parser.m2;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.StringTokenizer;
/**
* Parser that understands Maven version ranges of the form {@code (,1.0]} and such.
* More details about such ranges in Maven, can be found
* {@link https://cwiki.apache.org/confluence/display/MAVENOLD/Dependency+Mediation+and+Conflict+Resolution#DependencyMediationandConflictResolution-DependencyVersionRanges here}
*/
class MavenVersionRangeParser {
private static final DeweyDecimal javaVersion;
static {
DeweyDecimal v = null;
try {
v = new DeweyDecimal(System.getProperty("java.specification.version"));
} catch (Exception e) {
v = null;
}
javaVersion = v;
}
/**
* @param range The range to compare against
* @return Returns true if the current Java version, in which the instance of this class is running,
* is within the specified {@code range}. Else returns false.
*/
static boolean currentJavaVersionInRange(final String range) {
if (range == null) {
return false;
}
if (javaVersion == null) {
// this will almost never be the case, but if we couldn't
// determine the version of Java this system is running on,
// then there's nothing we can do
return false;
}
final Range parsedRange = parse(range);
return parsedRange != null && parsedRange.accepts(javaVersion);
}
/**
* @param range The range to compare against
* @param value The value being compared
* @return Compares the {@code value} against the {@code range} and returns true if the {@code value}
* lies within the {@code range}. Else returns false.
*/
static boolean rangeAccepts(final String range, final String value) {
if (value == null) {
return false;
}
final DeweyDecimal valToCompare;
try {
valToCompare = new DeweyDecimal(value);
} catch (NumberFormatException nfe) {
return false;
}
final Range parsedRange = parse(range);
return parsedRange != null && parsedRange.accepts(valToCompare);
}
private static Range parse(final String rangeValue) {
if (rangeValue == null || rangeValue.trim().isEmpty()) {
return null;
}
try {
// split the version by ","
final String[] versionParts = rangeValue.split(",");
if (versionParts.length == 1) {
final String boundVal = versionParts[0].trim();
final String stripped = stripBoundChars(boundVal);
if (stripped.isEmpty()) {
return null;
}
final DeweyDecimal bound = new DeweyDecimal(stripped);
return new BasicRange(bound, !boundVal.startsWith("("), bound, !boundVal.endsWith(")"));
}
if (versionParts.length == 2) {
final String lowerBoundVal = versionParts[0].trim();
final String strippedLowerBound = stripBoundChars(lowerBoundVal);
final DeweyDecimal lowerBound;
if (strippedLowerBound.isEmpty()) {
lowerBound = null;
} else {
lowerBound = new DeweyDecimal(strippedLowerBound);
}
final String upperBoundVal = versionParts[1].trim();
final String strippedUpperBound = stripBoundChars(upperBoundVal);
final DeweyDecimal upperBound;
if (strippedUpperBound.isEmpty()) {
upperBound = null;
} else {
upperBound = new DeweyDecimal(strippedUpperBound);
}
return new BasicRange(lowerBound, !lowerBoundVal.startsWith("("), upperBound, !upperBoundVal.endsWith(")"));
}
if (versionParts.length > 2) {
// each range part can itself be a range, which is valid in maven
final Collection<Range> ranges = new ArrayList<>();
for (int i = 0; i < versionParts.length; i = (i + 2 < versionParts.length) ? i + 2 : i + 1) {
final String partOne = versionParts[i];
final String partTwo;
if (i + 1 < versionParts.length) {
partTwo = versionParts[i + 1];
} else {
partTwo = "";
}
final Range rangePart = parse(partOne + "," + partTwo);
if (rangePart == null) {
continue;
}
ranges.add(rangePart);
}
return (ranges == null || ranges.isEmpty()) ? null : new MultiSetRange(ranges);
}
return null;
} catch (NumberFormatException nfe) {
return null;
}
}
private static String stripBoundChars(final String value) {
if (value == null) {
return null;
}
return value.replace("(", "").replace(")", "")
.replace("[", "").replace("]", "");
}
private interface Range {
boolean accepts(final DeweyDecimal value);
}
private static final class BasicRange implements Range {
private final DeweyDecimal lowerBound;
private final DeweyDecimal upperBound;
private final boolean lowerInclusive;
private final boolean upperInclusive;
private BasicRange(final DeweyDecimal lowerBound, final boolean lowerInclusive,
final DeweyDecimal upperBound, final boolean upperInclusive) {
this.lowerBound = lowerBound;
this.lowerInclusive = lowerInclusive;
this.upperBound = upperBound;
this.upperInclusive = upperInclusive;
}
@Override
public boolean accepts(final DeweyDecimal value) {
return value != null
&& (this.lowerBound == null || (this.lowerInclusive ? value.isGreaterThanOrEqual(lowerBound) : value.isGreaterThan(lowerBound)))
&& (this.upperBound == null || (this.upperInclusive ? value.isLessThanOrEqual(upperBound) : value.isLessThan(upperBound)));
}
}
private static final class MultiSetRange implements Range {
private final Collection<Range> ranges;
private MultiSetRange(final Collection<Range> ranges) {
this.ranges = ranges == null ? Collections.<Range>emptySet() : ranges;
}
@Override
public boolean accepts(final DeweyDecimal value) {
if (this.ranges.isEmpty()) {
return false;
}
for (final Range range : this.ranges) {
if (range == null) {
continue;
}
// if any range matches, we consider it a match
if (range.accepts(value)) {
return true;
}
}
return false;
}
}
// This class is a copy of the one in Ant project, but since Ivy *core* module
// (intentionally) doesn't rely on Ant, we use a copied version here
private static final class DeweyDecimal {
/**
* Array of components that make up DeweyDecimal
*/
private final int[] components;
/**
* Construct a DeweyDecimal from an array of integer components.
*
* @param components an array of integer components.
*/
@SuppressWarnings("unused")
public DeweyDecimal(final int[] components) {
this.components = new int[components.length];
System.arraycopy(components, 0, this.components, 0, components.length);
}
/**
* Construct a DeweyDecimal from string in DeweyDecimal format.
*
* @param string the string in dewey decimal format
* @throws NumberFormatException if string is malformed
*/
public DeweyDecimal(final String string)
throws NumberFormatException {
final StringTokenizer tokenizer = new StringTokenizer(string, ".", true);
final int size = tokenizer.countTokens();
components = new int[(size + 1) / 2];
for (int i = 0; i < components.length; i++) {
final String component = tokenizer.nextToken();
if (component.length() == 0) {
throw new NumberFormatException("Empty component in string");
}
components[i] = Integer.parseInt(component);
// Strip '.' token
if (tokenizer.hasMoreTokens()) {
tokenizer.nextToken();
// If it ended in a dot, throw an exception
if (!tokenizer.hasMoreTokens()) {
throw new NumberFormatException("DeweyDecimal ended in a '.'");
}
}
}
}
/**
* Return number of components in <code>DeweyDecimal</code>.
*
* @return the number of components in dewey decimal
*/
@SuppressWarnings("unused")
public int getSize() {
return components.length;
}
/**
* Return the component at specified index.
*
* @param index the index of components
* @return the value of component at index
*/
@SuppressWarnings("unused")
public int get(final int index) {
return components[index];
}
/**
* Return <code>true</code> if this <code>DeweyDecimal</code> is
* equal to the other <code>DeweyDecimal</code>.
*
* @param other the other DeweyDecimal
* @return true if equal to other DeweyDecimal, false otherwise
*/
public boolean isEqual(final DeweyDecimal other) {
final int max = Math.max(other.components.length, components.length);
for (int i = 0; i < max; i++) {
final int component1 = (i < components.length) ? components[i] : 0;
final int component2 = (i < other.components.length) ? other.components[i] : 0;
if (component2 != component1) {
return false;
}
}
return true; // Exact match
}
/**
* Return <code>true</code> if this <code>DeweyDecimal</code> is
* less than the other <code>DeweyDecimal</code>.
*
* @param other the other DeweyDecimal
* @return true if less than other DeweyDecimal, false otherwise
*/
public boolean isLessThan(final DeweyDecimal other) {
return !isGreaterThanOrEqual(other);
}
/**
* Return <code>true</code> if this <code>DeweyDecimal</code> is
* less than or equal to the other <code>DeweyDecimal</code>.
*
* @param other the other DeweyDecimal
* @return true if less than or equal to other DeweyDecimal, false otherwise
*/
public boolean isLessThanOrEqual(final DeweyDecimal other) {
return !isGreaterThan(other);
}
/**
* Return <code>true</code> if this <code>DeweyDecimal</code> is
* greater than the other <code>DeweyDecimal</code>.
*
* @param other the other DeweyDecimal
* @return true if greater than other DeweyDecimal, false otherwise
*/
public boolean isGreaterThan(final DeweyDecimal other) {
final int max = Math.max(other.components.length, components.length);
for (int i = 0; i < max; i++) {
final int component1 = (i < components.length) ? components[i] : 0;
final int component2 = (i < other.components.length) ? other.components[i] : 0;
if (component2 > component1) {
return false;
}
if (component2 < component1) {
return true;
}
}
return false; // Exact match
}
/**
* Return <code>true</code> if this <code>DeweyDecimal</code> is
* greater than or equal to the other <code>DeweyDecimal</code>.
*
* @param other the other DeweyDecimal
* @return true if greater than or equal to other DeweyDecimal, false otherwise
*/
public boolean isGreaterThanOrEqual(final DeweyDecimal other) {
final int max = Math.max(other.components.length, components.length);
for (int i = 0; i < max; i++) {
final int component1 = (i < components.length) ? components[i] : 0;
final int component2 = (i < other.components.length) ? other.components[i] : 0;
if (component2 > component1) {
return false;
}
if (component2 < component1) {
return true;
}
}
return true; // Exact match
}
/**
* Return string representation of <code>DeweyDecimal</code>.
*
* @return the string representation of DeweyDecimal.
*/
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
for (int component : components) {
if (sb.length() > 0) {
sb.append('.');
}
sb.append(component);
}
return sb.toString();
}
/**
* Compares this DeweyDecimal with another one.
*
* @param other another DeweyDecimal to compare with
* @return result
* @see java.lang.Comparable#compareTo(Object)
*/
@SuppressWarnings("unused")
public int compareTo(DeweyDecimal other) {
final int max = Math.max(other.components.length, components.length);
for (int i = 0; i < max; i++) {
final int component1 = (i < components.length) ? components[i] : 0;
final int component2 = (i < other.components.length) ? other.components[i] : 0;
if (component1 != component2) {
return component1 - component2;
}
}
return 0;
}
@Override
public int hashCode() {
return toString().hashCode();
}
@Override
public boolean equals(Object o) {
return o instanceof DeweyDecimal && isEqual((DeweyDecimal) o);
}
}
}