diff --git a/cli/src/main/java/com/devonfw/tools/ide/version/BoundaryType.java b/cli/src/main/java/com/devonfw/tools/ide/version/BoundaryType.java new file mode 100644 index 000000000..c8abed59a --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/version/BoundaryType.java @@ -0,0 +1,19 @@ +package com.devonfw.tools.ide.version; + +/** + * Enum representing the type of interval regarding its boundaries. + */ +public enum BoundaryType { + + /** Closed interval - includes the specified values at the boundaries. */ + CLOSED, + + /** Open interval - excludes the specified values at the boundaries. */ + OPEN, + + /** Left open interval - excludes the lower bound but includes the upper bound. */ + LEFT_OPEN, + + /** Right open interval - includes the lower bound but excludes the upper bound. */ + RIGHT_OPEN +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/version/VersionObject.java b/cli/src/main/java/com/devonfw/tools/ide/version/VersionObject.java index 5b4b4c871..c72e9d9f8 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/version/VersionObject.java +++ b/cli/src/main/java/com/devonfw/tools/ide/version/VersionObject.java @@ -3,12 +3,11 @@ /** * Abstract base interface for a version object such as {@link VersionIdentifier} and {@link VersionSegment}. * - * * {@link Comparable} for versions with an extended contract. If two versions are not strictly comparable (e.g. * "1.apple" and "1.banana") we fall back to some heuristics (e.g. lexicographical comparison for - * {@link VersionSegment#getLettersString() letters} that we do not understand (e.g. "apple" < "banana"). Therefore you can - * use {@link #compareVersion(Object)} to get a {@link VersionComparisonResult} that contains the additional information - * as {@link VersionComparisonResult#isUnsafe() unsafe} flag. + * {@link VersionSegment#getLettersString() letters} that we do not understand (e.g. "apple" < "banana"). Therefore, you + * can use {@link #compareVersion(Object)} to get a {@link VersionComparisonResult} that contains the additional + * information as {@link VersionComparisonResult#isUnsafe() unsafe} flag. * * @param type of the object to compare (this class itself). */ diff --git a/cli/src/main/java/com/devonfw/tools/ide/version/VersionRange.java b/cli/src/main/java/com/devonfw/tools/ide/version/VersionRange.java index 656117531..52d97c703 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/version/VersionRange.java +++ b/cli/src/main/java/com/devonfw/tools/ide/version/VersionRange.java @@ -4,7 +4,9 @@ import com.fasterxml.jackson.annotation.JsonValue; /** - * Container for a range of versions. + * Container for a range of versions. The lower and upper bounds can be exclusive or inclusive. If a bound is null, it + * means that this direction is unbounded. The boolean defining whether this bound is inclusive or exclusive is ignored + * in this case. */ public final class VersionRange implements Comparable { @@ -12,6 +14,10 @@ public final class VersionRange implements Comparable { private final VersionIdentifier max; + private final boolean leftIsExclusive; + + private final boolean rightIsExclusive; + /** * The constructor. * @@ -24,6 +30,42 @@ public VersionRange(VersionIdentifier min, VersionIdentifier max) { super(); this.min = min; this.max = max; + this.leftIsExclusive = false; + this.rightIsExclusive = false; + } + + /** + * The constructor. + * + * @param min the {@link #getMin() minimum}. + * @param max the {@link #getMax() maximum}. + * @param boundaryType the {@link BoundaryType} defining whether the boundaries of the range are inclusive or + * exclusive. + */ + public VersionRange(VersionIdentifier min, VersionIdentifier max, BoundaryType boundaryType) { + + super(); + this.min = min; + this.max = max; + this.leftIsExclusive = BoundaryType.LEFT_OPEN.equals(boundaryType) || BoundaryType.OPEN.equals(boundaryType); + this.rightIsExclusive = BoundaryType.RIGHT_OPEN.equals(boundaryType) || BoundaryType.OPEN.equals(boundaryType); + } + + /** + * The constructor. + * + * @param min the {@link #getMin() minimum}. + * @param max the {@link #getMax() maximum}. + * @param leftIsExclusive - {@code true} if the {@link #getMin() minimum} is exclusive, {@code false} otherwise. + * @param rightIsExclusive - {@code true} if the {@link #getMax() maximum} is exclusive, {@code false} otherwise. + */ + public VersionRange(VersionIdentifier min, VersionIdentifier max, boolean leftIsExclusive, boolean rightIsExclusive) { + + super(); + this.min = min; + this.max = max; + this.leftIsExclusive = leftIsExclusive; + this.rightIsExclusive = rightIsExclusive; } /** @@ -44,6 +86,38 @@ public VersionIdentifier getMax() { return this.max; } + /** + * @return {@code true} if the {@link #getMin() minimum} is exclusive, {@code false} otherwise. + */ + public boolean isLeftExclusive() { + + return this.leftIsExclusive; + } + + /** + * @return {@code true} if the {@link #getMax() maximum} is exclusive, {@code false} otherwise. + */ + public boolean isRightExclusive() { + + return this.rightIsExclusive; + } + + /** + * @return the {@link BoundaryType} defining whether the boundaries of the range are inclusive or exclusive. + */ + public BoundaryType getBoundaryType() { + + if (this.leftIsExclusive && this.rightIsExclusive) { + return BoundaryType.OPEN; + } else if (this.leftIsExclusive) { + return BoundaryType.LEFT_OPEN; + } else if (this.rightIsExclusive) { + return BoundaryType.RIGHT_OPEN; + } else { + return BoundaryType.CLOSED; + } + } + /** * @param version the {@link VersionIdentifier} to check. * @return {@code true} if the given {@link VersionIdentifier} is contained in this {@link VersionRange}, @@ -52,11 +126,17 @@ public VersionIdentifier getMax() { public boolean contains(VersionIdentifier version) { if (this.min != null) { + if (this.min.equals(version)) { + return !this.leftIsExclusive; + } if (version.isLess(this.min)) { return false; } } if (this.max != null) { + if (this.max.equals(version)) { + return !this.rightIsExclusive; + } if (version.isGreater(this.max)) { return false; } @@ -75,7 +155,37 @@ public int compareTo(VersionRange o) { } return -1; } - return this.min.compareTo(o.min); + int compareMins = this.min.compareTo(o.min); + if (compareMins == 0) { + return this.leftIsExclusive == o.leftIsExclusive ? 0 : this.leftIsExclusive ? 1 : -1; + } else { + return compareMins; + } + } + + @Override + public boolean equals(Object obj) { + + if (this == obj) + return true; + + if (obj == null || getClass() != obj.getClass()) + return false; + + VersionRange o = (VersionRange) obj; + + if (this.min == null && this.max == null) { + return o.min == null && o.max == null; + } + if (this.min == null) { + return o.min == null && this.max.equals(o.max) && this.rightIsExclusive == o.rightIsExclusive; + } + if (this.max == null) { + return this.min.equals(o.min) && o.max == null && this.leftIsExclusive == o.leftIsExclusive; + } + return this.min.equals(o.min) && this.leftIsExclusive == o.leftIsExclusive && this.max.equals(o.max) + && this.rightIsExclusive == o.rightIsExclusive; + } @Override @@ -107,6 +217,7 @@ public boolean equals(Object obj) { public String toString() { StringBuilder sb = new StringBuilder(); + sb.append(this.leftIsExclusive ? '(' : '['); if (this.min != null) { sb.append(this.min); } @@ -114,6 +225,7 @@ public String toString() { if (this.max != null) { sb.append(this.max); } + sb.append(this.rightIsExclusive ? ')' : ']'); return sb.toString(); } @@ -124,10 +236,29 @@ public String toString() { @JsonCreator public static VersionRange of(String value) { + boolean leftIsExclusive = false; + boolean rightIsExclusive = false; + + if (value.startsWith("(")) { + leftIsExclusive = true; + value = value.substring(1); + } + if (value.startsWith("[")) { + value = value.substring(1); + } + if (value.endsWith(")")) { + rightIsExclusive = true; + value = value.substring(0, value.length() - 1); + } + if (value.endsWith("]")) { + value = value.substring(0, value.length() - 1); + } + int index = value.indexOf('>'); if (index == -1) { return null; // log warning? } + VersionIdentifier min = null; if (index > 0) { min = VersionIdentifier.of(value.substring(0, index)); @@ -137,7 +268,7 @@ public static VersionRange of(String value) { if (!maxString.isEmpty()) { max = VersionIdentifier.of(maxString); } - return new VersionRange(min, max); + return new VersionRange(min, max, leftIsExclusive, rightIsExclusive); } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeContextTest.java b/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeContextTest.java index 87a2f2308..4f682aa78 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeContextTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeContextTest.java @@ -60,7 +60,8 @@ protected static IdeTestContext newContext(String projectName, String projectPat * in that project. * @return the {@link IdeTestContext} pointing to that project. */ - protected static IdeTestContext newContext(String projectName, String projectPath, boolean copyForMutation, String ... answers) { + protected static IdeTestContext newContext(String projectName, String projectPath, boolean copyForMutation, + String... answers) { Path sourceDir = PATH_PROJECTS.resolve(projectName); Path userDir = sourceDir; diff --git a/cli/src/test/java/com/devonfw/tools/ide/version/VersionRangeTest.java b/cli/src/test/java/com/devonfw/tools/ide/version/VersionRangeTest.java index 83f9209b8..0a305f8a6 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/version/VersionRangeTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/version/VersionRangeTest.java @@ -3,26 +3,150 @@ import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; +/** + * Test of {@link VersionRange}. + */ public class VersionRangeTest extends Assertions { + + /** + * Test of {@link VersionRange#of(String)}. + */ @Test - void testEquals() { + public void testOf() { + + // arrange + String v1String = "1.2>3"; + String v2String = "1>)"; + String v3String = "(1.2>3.4]"; + + // act + VersionRange v1 = VersionRange.of(v1String); + VersionRange v2 = VersionRange.of(v2String); + VersionRange v3 = VersionRange.of(v3String); + + // assert + // v1 + assertThat(v1.getMin()).isEqualTo(VersionIdentifier.of("1.2")); + assertThat(v1.getMax()).isEqualTo(VersionIdentifier.of("3")); + assertThat(v1.isLeftExclusive()).isFalse(); + assertThat(v1.isRightExclusive()).isFalse(); + // v2 + assertThat(v2.getMin()).isEqualTo(VersionIdentifier.of("1")); + assertThat(v2.getMax()).isEqualTo(null); + assertThat(v2.isLeftExclusive()).isFalse(); + assertThat(v2.isRightExclusive()).isTrue(); + // v3 + assertThat(v3.getMin()).isEqualTo(VersionIdentifier.of("1.2")); + assertThat(v3.getMax()).isEqualTo(VersionIdentifier.of("3.4")); + assertThat(v3.isLeftExclusive()).isTrue(); + assertThat(v3.isRightExclusive()).isFalse(); + } + + /** + * Test of {@link VersionRange#toString()}. + */ + @Test + public void testToString() { + + assertThat(VersionRange.of("1.2>3").toString()).isEqualTo("[1.2>3]"); + assertThat(VersionRange.of("1>)").toString()).isEqualTo("[1>)"); + assertThat(VersionRange.of("(1.2>3.4]").toString()).isEqualTo("(1.2>3.4]"); + } + /** + * Test of {@link VersionRange#equals(Object)}. + */ + @Test + public void testEquals() { + + // assert + // equals assertThat(VersionRange.of("1.2>")).isEqualTo(VersionRange.of("1.2>")); + assertThat(VersionRange.of("(1.2>")).isEqualTo(VersionRange.of("(1.2>)")); assertThat(VersionRange.of("1.2>3")).isEqualTo(VersionRange.of("1.2>3")); - assertThat(VersionRange.of(">3")).isEqualTo(VersionRange.of(">3")); + assertThat(VersionRange.of("[1.2>3")).isEqualTo(VersionRange.of("1.2>3]")); + assertThat(VersionRange.of(">3)")).isEqualTo(VersionRange.of(">3)")); assertThat(VersionRange.of(">")).isEqualTo(VersionRange.of(">")); + assertThat(VersionRange.of("[>)")).isEqualTo(VersionRange.of("(>]")); assertThat(VersionRange.of("8u302b08>11.0.14_9")).isEqualTo(VersionRange.of("8u302b08>11.0.14_9")); - + // not equals assertThat(VersionRange.of("1>")).isNotEqualTo(null); assertThat(VersionRange.of("1.2>")).isNotEqualTo(VersionRange.of("1>")); assertThat(VersionRange.of("1.2>3")).isNotEqualTo(VersionRange.of("1.2>")); + assertThat(VersionRange.of("(1.2>3")).isNotEqualTo(VersionRange.of("1.2.3>")); assertThat(VersionRange.of("1.2>3")).isNotEqualTo(VersionRange.of(">3")); - assertThat(VersionRange.of("1.2>")).isNotEqualTo(VersionRange.of("1.2>3")); + assertThat(VersionRange.of("[1.2>")).isNotEqualTo(VersionRange.of("[1.2>3")); assertThat(VersionRange.of(">3")).isNotEqualTo(VersionRange.of("1.2>3")); assertThat(VersionRange.of(">3")).isNotEqualTo(VersionRange.of(">")); assertThat(VersionRange.of(">")).isNotEqualTo(VersionRange.of(">3")); + assertThat(VersionRange.of("8u302b08>11.0.14_9")).isNotEqualTo(VersionRange.of("(8u302b08>11.0.14_9)")); assertThat(VersionRange.of("8u302b08>11.0.14_9")).isNotEqualTo(VersionRange.of("8u302b08>11.0.15_9")); assertThat(VersionRange.of("8u302b08>11.0.14_9")).isNotEqualTo(VersionRange.of("8u302b08>11.0.14_0")); + } + + /** + * Test of {@link VersionRange#contains(VersionIdentifier)} and testing if a {@link VersionIdentifier version} is + * contained in the {@link VersionRange}. + */ + @Test + public void testContains() { + + // assert + assertThat(VersionRange.of("1.2>3.4").contains(VersionIdentifier.of("1.2"))).isTrue(); + assertThat(VersionRange.of("1.2>3.4").contains(VersionIdentifier.of("2"))).isTrue(); + assertThat(VersionRange.of("1.2>3.4").contains(VersionIdentifier.of("3.4"))).isTrue(); + + assertThat(VersionRange.of("(1.2>3.4)").contains(VersionIdentifier.of("1.2.1"))).isTrue(); + assertThat(VersionRange.of("(1.2>3.4)").contains(VersionIdentifier.of("2"))).isTrue(); + assertThat(VersionRange.of("(1.2>3.4)").contains(VersionIdentifier.of("3.3.9"))).isTrue(); + } + + /** + * Test of {@link VersionRange#contains(VersionIdentifier)} and testing if a {@link VersionIdentifier version} is not + * contained in the {@link VersionRange}. + */ + @Test + public void testNotContains() { + + // assert + assertThat(VersionRange.of("1.2>3.4").contains(VersionIdentifier.of("1.1"))).isFalse(); + assertThat(VersionRange.of("1.2>3.4").contains(VersionIdentifier.of("3.4.1"))).isFalse(); + + assertThat(VersionRange.of("(1.2>3.4)").contains(VersionIdentifier.of("1.2"))).isFalse(); + assertThat(VersionRange.of("(1.2>3.4)").contains(VersionIdentifier.of("3.4"))).isFalse(); + } + + /** + * Test of {@link VersionRange#compareTo(VersionRange)} and testing if versions are compared to be the same. + */ + @Test + public void testCompareToIsSame() { + + // assert + assertThat(VersionRange.of("1.2>3").compareTo(VersionRange.of("1.2>3"))).isEqualTo(0); + assertThat(VersionRange.of("(1.2>3").compareTo(VersionRange.of("(1.2>3"))).isEqualTo(0); + assertThat(VersionRange.of("[1.2>3]").compareTo(VersionRange.of("[1.2>4)"))).isEqualTo(0); + } + + /** + * Test of {@link VersionRange#compareTo(VersionRange)} and testing if first version is smaller than second. + */ + @Test + public void testCompareToIsSmaller() { + + // assert + assertThat(VersionRange.of("1.1.2>3").compareTo(VersionRange.of("1.2>3"))).isEqualTo(-1); + assertThat(VersionRange.of("[1.2>3").compareTo(VersionRange.of("(1.2>4"))).isEqualTo(-1); + } + + /** + * Test of {@link VersionRange#compareTo(VersionRange)} and testing if first version is larger than second. + */ + @Test + public void testCompareToIsLarger() { + // assert + assertThat(VersionRange.of("1.2.1>3").compareTo(VersionRange.of("1.2>3"))).isEqualTo(1); + assertThat(VersionRange.of("(1.2>3").compareTo(VersionRange.of("1.2>4"))).isEqualTo(1); } }