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

name.remal.version.VersionRange Maven / Gradle / Ivy

package name.remal.version;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import net.jcip.annotations.Immutable;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.Serializable;
import java.util.Objects;

import static java.lang.String.format;

@Immutable
public final class VersionRange implements Serializable, Cloneable {

    @NotNull
    public static final VersionRange ANY_VERSION = new VersionRange(null, false, null, false);


    private static final long serialVersionUID = 1;

    private static final String VERSION_RAGE_CANT_BE_PARSED_MESSAGE_FORMAT = "Version range can't be parsed: '%s'. Only these formats are supported: '1.1+', '1.1.+', '[1,2]', '[1,2)', '(1,2]', '(1,2)'.";
    private static final String SUFFIXED_VERSIONS_CANT_BE_USED_MESSAGE_FORMAT = "Suffixed versions can't be used for version range: '%s'";


    @NotNull
    public static VersionRange create(@Nullable Version lowerBound, boolean lowerBoundInclusive, @Nullable Version upperBound, boolean upperBoundInclusive) {
        if (lowerBound == null && upperBound == null) return ANY_VERSION;
        return new VersionRange(lowerBound, lowerBoundInclusive, upperBound, upperBoundInclusive);
    }

    @NotNull
    public static VersionRange create(@NotNull Version version) {
        return new VersionRange(version, true, version, true);
    }


    @NotNull
    public static VersionRange greaterThan(@NotNull Version version) {
        return create(version, false, null, false);
    }

    @NotNull
    public static VersionRange greaterThanOrEqual(@NotNull Version version) {
        return create(version, true, null, false);
    }

    @NotNull
    public static VersionRange lessThan(@NotNull Version version) {
        return create(null, false, version, false);
    }

    @NotNull
    public static VersionRange lessThanOrEqual(@NotNull Version version) {
        return create(null, false, version, true);
    }


    @NotNull
    public static VersionRange parse(@NotNull String string) throws VersionRangeParsingException {
        string = string.trim();
        if (string.isEmpty()) {
            throw new VersionRangeParsingException("Blank string");
        }
        try {
            if ("+".equals(string)) {
                return ANY_VERSION;
            }

            if (string.contains("+")) {
                if (!string.endsWith("+")) {
                    throw new VersionRangeParsingException(format(VERSION_RAGE_CANT_BE_PARSED_MESSAGE_FORMAT, string));
                }
                if (string.contains("[") || string.contains("(") || string.contains("]") || string.contains(")")) {
                    throw new VersionRangeParsingException(format(VERSION_RAGE_CANT_BE_PARSED_MESSAGE_FORMAT, string));
                }
                String versionString = string.substring(0, string.length() - 1).trim();
                boolean endsWithDot = versionString.endsWith(".");
                if (endsWithDot) {
                    versionString = versionString.substring(0, versionString.length() - 1).trim();
                }
                Version version = Version.parse(versionString);
                if (version.hasSuffix()) {
                    throw new VersionRangeParsingException(format(SUFFIXED_VERSIONS_CANT_BE_USED_MESSAGE_FORMAT, string));
                }
                return create(
                    endsWithDot ? version.withNumber(version.getNumbersCount(), 0) : version,
                    true,
                    endsWithDot ? version.incrementNumber(version.getNumbersCount() - 1) : null,
                    false
                );
            }

            if (string.contains("[") || string.contains("(") || string.contains("]") || string.contains(")")) {
                if (!string.startsWith("[") && !string.startsWith("(")) {
                    throw new VersionRangeParsingException(format(VERSION_RAGE_CANT_BE_PARSED_MESSAGE_FORMAT, string));
                }
                if (!string.endsWith("]") && !string.endsWith(")")) {
                    throw new VersionRangeParsingException(format(VERSION_RAGE_CANT_BE_PARSED_MESSAGE_FORMAT, string));
                }

                int delimPos = string.indexOf(';');
                if (delimPos < 0) delimPos = string.indexOf(',');
                if (delimPos < 0) {
                    throw new VersionRangeParsingException(format(VERSION_RAGE_CANT_BE_PARSED_MESSAGE_FORMAT, string));
                }

                final Version lowerBound;
                {
                    String lowerBoundString = string.substring(1, delimPos).trim();
                    if (!lowerBoundString.isEmpty()) {
                        lowerBound = Version.parse(lowerBoundString);
                        if (lowerBound.hasSuffix()) {
                            throw new VersionRangeParsingException(format(SUFFIXED_VERSIONS_CANT_BE_USED_MESSAGE_FORMAT, string));
                        }
                    } else {
                        lowerBound = null;
                    }
                }

                final Version upperBound;
                {
                    String upperBoundString = string.substring(delimPos + 1, string.length() - 1).trim();
                    if (!upperBoundString.isEmpty()) {
                        upperBound = Version.parse(upperBoundString);
                        if (upperBound.hasSuffix()) {
                            throw new VersionRangeParsingException(format(SUFFIXED_VERSIONS_CANT_BE_USED_MESSAGE_FORMAT, string));
                        }
                    } else {
                        upperBound = null;
                    }
                }

                return create(
                    lowerBound,
                    string.startsWith("["),
                    upperBound,
                    string.endsWith("]")
                );
            }

            {
                Version version = Version.parse(string);
                return create(version);
            }

        } catch (Exception e) {
            throw e instanceof VersionRangeParsingException ? (VersionRangeParsingException) e : new VersionRangeParsingException(e);
        }
    }

    @Nullable
    @Contract("null->null")
    @JsonCreator
    public static VersionRange parseOrNull(@Nullable String string) {
        if (string == null || string.isEmpty()) return null;
        try {
            return parse(string);
        } catch (VersionRangeParsingException ignored) {
            return null;
        }
    }


    @Nullable
    private final Version lowerBound;

    private final boolean lowerBoundInclusive;

    @Nullable
    private final Version upperBound;

    private final boolean upperBoundInclusive;

    private VersionRange(@Nullable Version lowerBound, boolean lowerBoundInclusive, @Nullable Version upperBound, boolean upperBoundInclusive) {
        if (lowerBound != null && upperBound != null) {
            if (lowerBound.compareTo(upperBound) > 0) {
                throw new IllegalArgumentException(format("Lower bound version '%s' greater then upper bound '%s'", lowerBound, upperBound));
            }
            if (lowerBound.equals(upperBound) && (lowerBoundInclusive || upperBoundInclusive)) {
                lowerBoundInclusive = true;
                upperBoundInclusive = true;
            }
        }
        this.lowerBound = lowerBound;
        this.lowerBoundInclusive = lowerBound != null && lowerBoundInclusive;
        this.upperBound = upperBound;
        this.upperBoundInclusive = upperBound != null && upperBoundInclusive;
    }

    public boolean contains(@NotNull Version version) {
        if (lowerBound != null) {
            int comparisonResult = lowerBound.compareTo(version);
            if (lowerBoundInclusive && 0 < comparisonResult) return false;
            if (!lowerBoundInclusive && 0 <= comparisonResult) return false;
        }
        if (upperBound != null) {
            int comparisonResult = upperBound.compareTo(version);
            if (upperBoundInclusive && comparisonResult < 0) return false;
            if (!upperBoundInclusive && comparisonResult <= 0) return false;
        }
        return true;
    }

    public boolean contains(@NotNull VersionRange versionRange) {
        if (versionRange.lowerBound == null && versionRange.upperBound == null) {
            return lowerBound == null && upperBound == null;
        }
        if (versionRange.lowerBound != null) {
            if (!versionRange.lowerBoundInclusive && !lowerBoundInclusive) {
                if (!versionRange.lowerBound.equals(lowerBound) && !contains(versionRange.lowerBound)) return false;
            } else {
                if (!contains(versionRange.lowerBound)) return false;
            }
        }
        if (versionRange.upperBound != null) {
            if (!versionRange.upperBoundInclusive && !upperBoundInclusive) {
                if (!versionRange.upperBound.equals(upperBound) && !contains(versionRange.upperBound)) return false;
            } else {
                if (!contains(versionRange.upperBound)) return false;
            }
        }
        return true;
    }

    public boolean isBoundToVersion() {
        return lowerBoundInclusive && upperBoundInclusive && lowerBound != null && lowerBound.equals(upperBound);
    }

    @Nullable
    public Version getLowerBound() {
        return lowerBound;
    }

    public boolean isLowerBoundInclusive() {
        return lowerBoundInclusive;
    }

    @Nullable
    public Version getUpperBound() {
        return upperBound;
    }

    public boolean isUpperBoundInclusive() {
        return upperBoundInclusive;
    }

    @Override
    @NotNull
    @JsonValue
    public String toString() {
        if (lowerBound == null && upperBound == null) {
            return "+";
        }

        if (lowerBoundInclusive && upperBoundInclusive && lowerBound != null && lowerBound.equals(upperBound)) {
            return lowerBound.toString();
        }

        if (lowerBoundInclusive && !upperBoundInclusive && lowerBound != null && upperBound != null) {
            int lowerBoundNumberCount = lowerBound.getNumbersCount();
            int upperBoundNumberCount = upperBound.getNumbersCount();
            if (lowerBoundNumberCount - 1 == upperBoundNumberCount && 0 == lowerBound.getNumberOr0(lowerBoundNumberCount - 1)) {
                Version cutLowerBound = lowerBound.withoutNumber(lowerBoundNumberCount - 1);
                if (upperBound.equals(cutLowerBound.incrementNumber(upperBoundNumberCount - 1))) {
                    return cutLowerBound + ".+";
                }
            }
        }

        StringBuilder sb = new StringBuilder();
        if (lowerBoundInclusive) {
            sb.append('[');
        } else {
            sb.append('(');
        }
        if (lowerBound != null) {
            sb.append(lowerBound);
        }
        sb.append(',');
        if (upperBound != null) {
            sb.append(upperBound);
        }
        if (upperBoundInclusive) {
            sb.append(']');
        } else {
            sb.append(')');
        }
        return sb.toString();
    }

    @Override
    public boolean equals(@Nullable Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof VersionRange)) return false;
        VersionRange other = (VersionRange) obj;
        if (lowerBoundInclusive != other.lowerBoundInclusive) return false;
        if (upperBoundInclusive != other.upperBoundInclusive) return false;
        if (!Objects.equals(lowerBound, other.lowerBound)) return false;
        if (!Objects.equals(upperBound, other.upperBound)) return false;
        return true;
    }

    @Override
    public int hashCode() {
        int result = 1;
        final int prime = 31;
        result = prime * result + Objects.hashCode(lowerBound);
        result = prime * result + Boolean.hashCode(lowerBoundInclusive);
        result = prime * result + Objects.hashCode(upperBound);
        result = prime * result + Boolean.hashCode(upperBoundInclusive);
        return result;
    }

    @Override
    @NotNull
    public VersionRange clone() {
        return new VersionRange(lowerBound, lowerBoundInclusive, upperBound, upperBoundInclusive);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy