All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.octopusden.releng.versions.VersionRange Maven / Gradle / Ivy

The newest version!
package org.octopusden.releng.versions;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * Simple implementation maven based version range specification.
 * The original maven implementation is not used because of the problem with comparing
 * versions in different formats, e.g (2.0-1, 3.1-5) will not contain "2.0.2" version.
 */
public final class VersionRange {
    private static final int MINIMUM_RANGE_LENGTH = 3;
    private final String versionRange;
    private final List versionRestrictions = new ArrayList<>();

    private VersionRange(final String versionRange) {
        this.versionRange = versionRange;
        Arrays.stream(versionRange.split("(?<=[])])\\s*,\\s*(?=[\\[(])")).map(VersionRestriction::parseInternal).forEach(versionRestrictions::add);
        for (int i = 0; i < versionRestrictions.size() - 1; i++) {
            if (versionRestrictions.get(i).right != null && versionRestrictions.get(i + 1).left != null &&
                    versionRestrictions.get(i).right.compareTo(versionRestrictions.get(i + 1).left) > 0) {
                throw new IllegalArgumentException("Bad range: the previous range " +
                        versionRestrictions.get(i).source + " overlaps next " + versionRestrictions.get(i + 1).source);
            }
        }
    }

    /**
     * Create version range from maven based {@link String} representation.
     * @see Dependency Version Ranges
     * It is used maven
     * @param versionRange version range
     * @return Returns created version range instance
     */
    public static VersionRange createFromVersionSpec(final String versionRange) {
        return new VersionRange(versionRange);
    }

    /**
     * Check if the specified version is in range.
     * @param version version to check
     * @return Returns true if the given version is in range, false otherwise
     */
    public boolean containsVersion(final IVersionInfo version) {
        return versionRestrictions.stream().anyMatch(versionRange -> versionRange.containsVersion(version));
    }

    /**
     * Check if the given version range intersects with this.
     * @param other version range to check for intersections with this
     * @return Returns true if the given version range intersects with this, false otherwise
     */
    public boolean isIntersect(final VersionRange other) {
        return versionRestrictions.stream().anyMatch(left -> {
            if (left.hardVersion) {
                return other.versionRestrictions.stream().anyMatch(right -> right.containsVersion(left.left));
            }
            return other.versionRestrictions.stream().anyMatch(right -> right.hardVersion && left.containsVersion(right.right) || !(
                    compareLeftLeftToRightLeft(left, right) < 0 && compareLeftRightToRightLeft(left, right) < 0 ||
                            compareLeftLeftToRightRight(left, right) > 0 && compareLeftRightToRightRight(left, right) > 0));
        });
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        final VersionRange that = (VersionRange) o;
        return versionRange.equals(that.versionRange);
    }

    @Override
    public int hashCode() {
        return Objects.hash(versionRange);
    }

    @Override
    public String toString() {
        return "VersionRange='" + versionRange + '\'';
    }

    private static int restrictedCompare(final IVersionInfo left, final boolean leftIncluded,
                                         final IVersionInfo right, final boolean rightIncluded,
                                         int restrictedValue) {
        final int result = left.compareTo(right);
        if (result != 0 || leftIncluded && rightIncluded) {
            return result;
        }
        return restrictedValue;
    }

    private static int compareLeftLeftToRightLeft(final VersionRestriction left, final VersionRestriction right) {
        return left.left == null && right.left == null ? 0 : left.left == null ? -1 : right.left == null ? 1 :
                restrictedCompare(left.left, left.includeLeft, right.left, right.includeRight, -1);
    }

    private static int compareLeftLeftToRightRight(final VersionRestriction left, final VersionRestriction right) {
        return left.left == null && right.right == null ? 0 : left.left == null ? -1 : right.right == null ? 1 :
                restrictedCompare(left.left, left.includeLeft, right.right, right.includeRight, 1);
    }

    private static int compareLeftRightToRightLeft(final VersionRestriction left, final VersionRestriction right) {
        return left.right == null && right.left == null ? 0 : left.right == null || right.left == null ? 1 :
                restrictedCompare(left.right, left.includeRight, right.left, right.includeLeft, -1);
    }

    private static int compareLeftRightToRightRight(final VersionRestriction left, final VersionRestriction right) {
        return left.right == null && right.right == null ? 0 : left.right == null ? 1 : right.right == null ? -1 :
                restrictedCompare(left.right, left.includeRight, right.right, right.includeRight, -1);
    }

    private static final class VersionRestriction {
        private final String source;
        private final IVersionInfo left;
        private final boolean includeLeft;
        private final IVersionInfo right;
        private final boolean includeRight;
        private final boolean hardVersion;

        private VersionRestriction(String source, IVersionInfo left, boolean includeLeft, IVersionInfo right, boolean includeRight) {
            if (left == null && right == null) {
                throw new IllegalArgumentException("Bad range: no minimum, maximum allowed versions are specified " + source);
            }
            this.source = source;
            this.left = left;
            this.includeLeft = includeLeft;
            this.right = right;
            this.includeRight = includeRight;
            hardVersion = left != null &&  right != null && left.compareTo(right) == 0;
        }

        private boolean containsVersion(final IVersionInfo version) {
            Objects.requireNonNull(version, "Version can't be null");
            if (left != null && (includeLeft && left.compareTo(version) > 0 || !includeLeft && left.compareTo(version) >= 0)) {
                return false;
            }
            if (right != null && (includeRight && right.compareTo(version) < 0 || !includeRight && right.compareTo(version) <= 0)) {
                return false;
            }
            return true;
        }

        private static VersionRestriction parseInternal(final String versionRange) {
            final String[] versionRangeParts = versionRange.trim().split("\\s*,\\s*");
            if (versionRangeParts[0].length() == 0) {
                throw new IllegalArgumentException("Bad version range: there is no 'minimum' specification " + versionRange);
            }
            if (versionRangeParts.length == 1) {
                if (versionRangeParts[0].charAt(0) != '[') {
                    throw new IllegalArgumentException("Bad version range: the '[' doesn't specify hard version: " + versionRange);
                }
                if (versionRangeParts[0].length() < MINIMUM_RANGE_LENGTH || versionRangeParts[0].charAt(versionRangeParts[0].length() - 1) != ']') {
                    throw new IllegalArgumentException("Bad version range: the ']' doesn't specify hard version: " + versionRange);
                }
                final IVersionInfo version = NumericVersion.parse(versionRangeParts[0].substring(1, versionRangeParts[0].length() - 1));
                return new VersionRestriction(versionRange, version, true, version, true);
            }

            if (versionRangeParts.length > 2) {
                throw new IllegalArgumentException("Bad version range: too many versions " + versionRange);
            }

            if (versionRangeParts[1].length() == 0) {
                throw new IllegalArgumentException("Bad version range: there is no 'maximum' specification " + versionRange);
            }
            final boolean includeLeft;
            final boolean includeRight;
            final String leftVersion;
            final String rightVersion;
            switch (versionRangeParts[0].charAt(0)) {
                case '[':
                    if (versionRangeParts[0].length() == 1) {
                        throw new IllegalArgumentException("Bad version range: the '[' doesn't specify start of range: " + versionRange);
                    }
                    includeLeft = true;
                    leftVersion = versionRangeParts[0].substring(1);
                    break;
                case '(':
                    includeLeft = false;
                    leftVersion = versionRangeParts[0].length() == 1 ? null : versionRangeParts[0].substring(1);
                    break;
                default:
                    throw new IllegalArgumentException("Bad version range: the 'soft' requirement isn't supported: " + versionRange);
            }

            switch (versionRangeParts[1].charAt(versionRangeParts[1].length() - 1)) {
                case ']':
                    if (versionRangeParts[1].length() == 1) {
                        throw new IllegalArgumentException("Bad version range: the ']' doesn't specify end of range: " + versionRange);
                    }
                    includeRight = true;
                    rightVersion = versionRangeParts[1].substring(0, versionRangeParts[1].length() - 1);
                    break;
                case ')':
                    includeRight = false;
                    rightVersion = versionRangeParts[1].length() == 1 ? null : versionRangeParts[1].substring(0, versionRangeParts[1].length() - 1);
                    break;
                default:
                    throw new IllegalArgumentException("Bad version range: the 'soft' requirement isn't supported: " + versionRange);
            }
            final IVersionInfo minimum = leftVersion != null ? NumericVersion.parse(leftVersion) : null;
            final IVersionInfo maximum = rightVersion != null ? NumericVersion.parse(rightVersion) : null;
            if (minimum != null && maximum != null) {
                if (minimum.compareTo(maximum) == 0) {
                    if (!includeLeft) {
                        throw new IllegalArgumentException("Bad version range: the '[' doesn't start range: " + versionRange);
                    }
                    if (!includeRight) {
                        throw new IllegalArgumentException("Bad version range: the ']' doesn't start range: " + versionRange);
                    }
                }
                if (minimum.compareTo(maximum) > 0) {
                    throw new IllegalArgumentException("Bad version range: the left (minimal) is greater than right (maximum): " + versionRange);
                }
            }
            return new VersionRestriction(versionRange, minimum, includeLeft, maximum, includeRight);
        }

        @Override
        public String toString() {
            return "VersionRange{" +
                    "source='" + source + '\'' +
                    ", left=" + left +
                    ", includeLeft=" + includeLeft +
                    ", right=" + right +
                    ", includeRight=" + includeRight +
                    '}';
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy