• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

devonfw / IDEasy / 19805867766

30 Nov 2025 10:35PM UTC coverage: 69.845% (+0.05%) from 69.793%
19805867766

push

github

web-flow
#1617: fix CVE generation #1624: tolerant versionrange parsing (#1625)

3825 of 6007 branches covered (63.68%)

Branch coverage included in aggregate %.

9799 of 13499 relevant lines covered (72.59%)

3.16 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.92
cli/src/main/java/com/devonfw/tools/ide/version/VersionRange.java
1
package com.devonfw.tools.ide.version;
2

3
import java.util.Objects;
4

5
/**
6
 * 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.
7
 * The boolean defining whether this bound is inclusive or exclusive is ignored in this case.
8
 */
9
public final class VersionRange implements Comparable<VersionRange>, GenericVersionRange {
10

11
  /** The unbounded {@link VersionRange} instance. */
12
  public static final VersionRange UNBOUNDED = new VersionRange(null, null, BoundaryType.OPEN);
8✔
13

14
  private static final String VERSION_SEPARATOR = ",";
15

16
  final VersionIdentifier min;
17

18
  final VersionIdentifier max;
19

20
  final BoundaryType boundaryType;
21

22
  /**
23
   * The constructor.
24
   *
25
   * @param min the {@link #getMin() minimum}.
26
   * @param max the {@link #getMax() maximum}.
27
   * @param boundaryType the {@link BoundaryType} defining whether the boundaries of the range are inclusive or exclusive.
28
   */
29
  private VersionRange(VersionIdentifier min, VersionIdentifier max, BoundaryType boundaryType) {
30

31
    super();
2✔
32
    Objects.requireNonNull(boundaryType);
3✔
33
    this.min = min;
3✔
34
    this.max = max;
3✔
35
    this.boundaryType = boundaryType;
3✔
36
    if ((min != null) && (max != null) && min.isGreater(max)) {
8✔
37
      throw new IllegalArgumentException(toString());
6✔
38
    } else if ((min == null) && !boundaryType.isLeftExclusive()) {
5✔
39
      throw new IllegalArgumentException(toString());
6✔
40
    } else if ((max == null) && !boundaryType.isRightExclusive()) {
5✔
41
      throw new IllegalArgumentException(toString());
6✔
42
    }
43

44
  }
1✔
45

46
  @Override
47
  public VersionIdentifier getMin() {
48

49
    return this.min;
3✔
50
  }
51

52
  @Override
53
  public VersionIdentifier getMax() {
54

55
    return this.max;
3✔
56
  }
57

58
  @Override
59
  public BoundaryType getBoundaryType() {
60

61
    return this.boundaryType;
3✔
62
  }
63

64
  @Override
65
  public boolean isPattern() {
66

67
    return true;
2✔
68
  }
69

70
  @Override
71
  public boolean contains(VersionIdentifier version) {
72

73
    VersionSegment start = version.getStart();
3✔
74
    if ((start.getNumber() == -1) && start.isPattern()) {
7!
75
      return true; // * and *! are always contained
2✔
76
    }
77
    if (this.min != null) {
3!
78
      VersionComparisonResult compareMin = version.compareVersion(this.min);
5✔
79
      if (compareMin.isLess()) {
3✔
80
        return false;
2✔
81
      } else if (compareMin.isEqual() && this.boundaryType.isLeftExclusive() && !version.isPattern()) {
10✔
82
        return false;
2✔
83
      }
84
    }
85
    if (this.max != null) {
3✔
86
      VersionComparisonResult compareMax = version.compareVersion(this.max);
5✔
87
      if (compareMax.isGreater()) {
3✔
88
        return false;
2✔
89
      } else if (compareMax.isEqual() && this.boundaryType.isRightExclusive()) {
7✔
90
        return false;
2✔
91
      }
92
    }
93
    return true;
2✔
94
  }
95

96
  @Override
97
  public int compareTo(VersionRange o) {
98

99
    if (this.min == null) {
3!
100
      if (o == null) {
×
101
        return 1; // should never happen
×
102
      } else if (o.min == null) {
×
103
        return 0;
×
104
      }
105
      return -1;
×
106
    }
107
    int compareMins = this.min.compareTo(o.min);
6✔
108
    if (compareMins == 0) {
2✔
109
      return this.boundaryType.isLeftExclusive() == o.boundaryType.isLeftExclusive() ? 0
10✔
110
          : this.boundaryType.isLeftExclusive() ? 1 : -1;
7✔
111
    } else {
112
      return compareMins;
2✔
113
    }
114
  }
115

116
  /**
117
   * @param other the {@link VersionRange} to unite with.
118
   * @return the union of this with the given {@link VersionRange} or {@code null} if not {@link VersionRangeRelation#CONNECTED connected} or
119
   *     {@link VersionRangeRelation#OVERLAPPING overlapping}.
120
   */
121
  public VersionRange union(VersionRange other) {
122

123
    return union(other, VersionRangeRelation.CONNECTED);
5✔
124
  }
125

126
  /**
127
   * @param other the {@link VersionRange} to unite with.
128
   * @param minRelation the minimum {@link VersionRangeRelation} required to allow building the union instead of returning {@code null}. So if you want to
129
   *     build a strict union, you can pass {@link VersionRangeRelation#OVERLAPPING} so you only get a union that contains exactly what is contained in at least
130
   *     one of the two {@link VersionRange}s. However, you can pass {@link VersionRangeRelation#CONNECTED_LOOSELY} in order to get "[2.0,5.0]" as the union of
131
   *     "[2.0,2.2]" and "[2.3,5.0]".
132
   * @return the union of this with the given {@link VersionRange} or {@code null} if the actual {@link VersionRangeRelation} of the {@link VersionRange}s is
133
   *     lower than the given {@code minRelation}.
134
   */
135
  public VersionRange union(VersionRange other, VersionRangeRelation minRelation) {
136

137
    if (other == null) {
2!
138
      return this;
×
139
    }
140
    return new VersionRangeCombination(this, other).union(minRelation);
8✔
141
  }
142

143
  /**
144
   * @param other the {@link VersionRange} to intersect with.
145
   * @return the intersection of this with the given {@link VersionRange} or {@code null} for empty intersection.
146
   */
147
  public VersionRange intersect(VersionRange other) {
148

149
    if (other == null) {
2!
150
      return this;
×
151
    }
152
    return new VersionRangeCombination(this, other).intersection();
7✔
153
  }
154

155
  @Override
156
  public boolean equals(Object obj) {
157

158
    if (this == obj) {
3✔
159
      return true;
2✔
160
    } else if ((obj == null) || (getClass() != obj.getClass())) {
7!
161
      return false;
2✔
162
    }
163
    VersionRange o = (VersionRange) obj;
3✔
164
    if (this.boundaryType != o.boundaryType) {
5✔
165
      return false;
2✔
166
    } else if (!Objects.equals(this.min, o.min)) {
6✔
167
      return false;
2✔
168
    } else if (!Objects.equals(this.max, o.max)) {
6✔
169
      return false;
2✔
170
    }
171
    return true;
2✔
172
  }
173

174
  @Override
175
  public String toString() {
176

177
    StringBuilder sb = new StringBuilder();
4✔
178
    sb.append(this.boundaryType.getPrefix());
6✔
179
    if (this.min != null) {
3✔
180
      sb.append(this.min);
5✔
181
    }
182
    sb.append(VERSION_SEPARATOR);
4✔
183
    if (this.max != null) {
3✔
184
      sb.append(this.max);
5✔
185
    }
186
    sb.append(this.boundaryType.getSuffix());
6✔
187
    return sb.toString();
3✔
188
  }
189

190
  /**
191
   * @param value the {@link #toString() string representation} of a {@link VersionRange} to parse.
192
   * @return the parsed {@link VersionRange}.
193
   */
194
  public static VersionRange of(String value) {
195

196
    return of(value, false);
4✔
197
  }
198

199
  /**
200
   * @param value the {@link #toString() string representation} of a {@link VersionRange} to parse.
201
   * @param tolerance {@code true} to enable tolerant parsing so we can read garbage (e.g. form JSON) without failing.
202
   * @return the parsed {@link VersionRange}.
203
   */
204
  public static VersionRange of(String value, boolean tolerance) {
205

206
    Boolean isleftExclusive = null;
2✔
207
    Boolean isRightExclusive = null;
2✔
208
    if (value.startsWith(BoundaryType.START_EXCLUDING_PREFIX)) {
4✔
209
      isleftExclusive = Boolean.TRUE;
2✔
210
      value = value.substring(BoundaryType.START_EXCLUDING_PREFIX.length());
6✔
211
    } else if (value.startsWith(BoundaryType.START_INCLUDING_PREFIX)) {
4✔
212
      isleftExclusive = Boolean.FALSE;
2✔
213
      value = value.substring(BoundaryType.START_INCLUDING_PREFIX.length());
5✔
214
    }
215
    if (value.endsWith(BoundaryType.END_EXCLUDING_SUFFIX)) {
4✔
216
      isRightExclusive = Boolean.TRUE;
2✔
217
      value = value.substring(0, value.length() - BoundaryType.END_EXCLUDING_SUFFIX.length());
10✔
218
    } else if (value.endsWith(BoundaryType.END_INCLUDING_SUFFIX)) {
4✔
219
      isRightExclusive = Boolean.FALSE;
2✔
220
      value = value.substring(0, value.length() - BoundaryType.END_INCLUDING_SUFFIX.length());
9✔
221
    }
222
    VersionIdentifier min = null;
2✔
223
    VersionIdentifier max = null;
2✔
224
    int index = value.indexOf(VERSION_SEPARATOR);
4✔
225
    if (index < 0) {
2✔
226
      min = VersionIdentifier.of(value);
3✔
227
      max = min;
3✔
228
    } else {
229
      String minString = value.substring(0, index);
5✔
230
      if (!minString.isBlank()) {
3✔
231
        min = VersionIdentifier.of(minString);
3✔
232
        if (min == VersionIdentifier.LATEST) {
3✔
233
          min = null;
2✔
234
        }
235
      }
236
      String maxString = value.substring(index + 1);
6✔
237
      if (!maxString.isBlank()) {
3✔
238
        max = VersionIdentifier.of(maxString);
3✔
239
        if (max == VersionIdentifier.LATEST) {
3✔
240
          max = null;
2✔
241
        }
242
      }
243
    }
244
    if ((isleftExclusive == null) || (tolerance && (min == null))) {
6✔
245
      isleftExclusive = min == null;
7✔
246
    }
247
    if ((isRightExclusive == null) || (tolerance && (max == null))) {
6✔
248
      isRightExclusive = max == null;
7✔
249
    }
250

251
    if ((min == null) && (max == null) && isleftExclusive && isRightExclusive) {
10✔
252
      return UNBOUNDED;
2✔
253
    }
254
    return new VersionRange(min, max, BoundaryType.of(isleftExclusive, isRightExclusive));
11✔
255
  }
256

257
  /**
258
   * @param min the {@link #getMin() minimum}.
259
   * @param max the {@link #getMax() maximum}.
260
   * @param type the {@link BoundaryType} defining whether the boundaries of the range are inclusive or exclusive.
261
   * @return the {@link VersionRange} created from the given values.
262
   */
263
  public static VersionRange of(VersionIdentifier min, VersionIdentifier max, BoundaryType type) {
264

265
    if (type == null) {
2!
266
      type = BoundaryType.of(min == null, max == null);
×
267
    }
268
    if ((min == null) && (max == null)) {
4✔
269
      assert (type == BoundaryType.OPEN);
4!
270
      return UNBOUNDED;
2✔
271
    }
272
    return new VersionRange(min, max, type);
7✔
273
  }
274

275
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc