Skip to content

Commit

Permalink
devonfw#158: VersionRange with open boundaries
Browse files Browse the repository at this point in the history
Extended the VersionRange to also model open boundaries that do not include the specified value. Also wrote tests for this class.
  • Loading branch information
MattesMrzik committed Dec 18, 2023
1 parent 1d60d9c commit 9574f8d
Show file tree
Hide file tree
Showing 4 changed files with 308 additions and 7 deletions.
19 changes: 19 additions & 0 deletions cli/src/main/java/com/devonfw/tools/ide/version/BoundaryType.java
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 <T> type of the object to compare (this class itself).
*/
Expand Down
137 changes: 134 additions & 3 deletions cli/src/main/java/com/devonfw/tools/ide/version/VersionRange.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package com.devonfw.tools.ide.version;

/**
* 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<VersionRange> {

private final VersionIdentifier min;

private final VersionIdentifier max;

private final boolean leftIsExclusive;

private final boolean rightIsExclusive;

/**
* The constructor.
*
Expand All @@ -20,6 +26,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;
}

/**
Expand All @@ -38,6 +80,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},
Expand All @@ -46,11 +120,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;
}
Expand All @@ -69,20 +149,52 @@ 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
public String toString() {

StringBuilder sb = new StringBuilder();
sb.append(this.leftIsExclusive ? '(' : '[');
if (this.min != null) {
sb.append(this.min);
}
sb.append('>');
if (this.max != null) {
sb.append(this.max);
}
sb.append(this.rightIsExclusive ? ')' : ']');
return sb.toString();
}

Expand All @@ -92,10 +204,29 @@ public String toString() {
*/
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));
Expand All @@ -105,7 +236,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);
}

}
Loading

0 comments on commit 9574f8d

Please sign in to comment.