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

devonfw / IDEasy / 18597783582

17 Oct 2025 03:45PM UTC coverage: 68.522% (+0.09%) from 68.437%
18597783582

Pull #1467

github

web-flow
Merge 4e5b40da9 into 46d29352c
Pull Request #1467: #907 Add yarn and corepack

3467 of 5539 branches covered (62.59%)

Branch coverage included in aggregate %.

9054 of 12734 relevant lines covered (71.1%)

3.13 hits per line

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

89.11
cli/src/main/java/com/devonfw/tools/ide/version/VersionSegment.java
1
package com.devonfw.tools.ide.version;
2

3
import java.util.Objects;
4

5
/**
6
 * Represents a single segment of a {@link VersionIdentifier}.
7
 */
8
public class VersionSegment implements VersionObject<VersionSegment> {
9

10
  /** Pattern to match a any version that matches the prefix. Value is: {@value} */
11
  public static final String PATTERN_MATCH_ANY_VERSION = "*!";
12

13
  /** Pattern to match a {@link VersionPhase#isStable() stable} version that matches the prefix. Value is: {@value} */
14
  public static final String PATTERN_MATCH_ANY_STABLE_VERSION = "*";
15

16
  private static final VersionSegment EMPTY = new VersionSegment("", "", "", "");
9✔
17

18
  private final String separator;
19

20
  private final VersionLetters letters;
21

22
  private final String pattern;
23

24
  private final String digits;
25

26
  private final int number;
27

28
  private VersionSegment next;
29

30
  /**
31
   * The constructor.
32
   *
33
   * @param separator the {@link #getSeparator() separator}.
34
   * @param letters the {@link #getLettersString() letters}.
35
   * @param digits the {@link #getDigits() digits}.
36
   */
37
  VersionSegment(String separator, String letters, String digits) {
38

39
    this(separator, letters, digits, "");
6✔
40
  }
1✔
41

42
  /**
43
   * The constructor.
44
   *
45
   * @param separator the {@link #getSeparator() separator}.
46
   * @param letters the {@link #getLettersString() letters}.
47
   * @param digits the {@link #getDigits() digits}.
48
   * @param pattern the {@link #getPattern() pattern}.
49
   */
50
  VersionSegment(String separator, String letters, String digits, String pattern) {
51

52
    super();
2✔
53
    this.separator = separator;
3✔
54
    this.letters = VersionLetters.of(letters);
4✔
55
    if (!pattern.isEmpty() && !PATTERN_MATCH_ANY_STABLE_VERSION.equals(pattern)
9✔
56
        && !PATTERN_MATCH_ANY_VERSION.equals(pattern)) {
2!
57
      throw new IllegalArgumentException("Invalid pattern: " + pattern);
×
58
    }
59
    this.pattern = pattern;
3✔
60
    /*
61
     * this.lettersLower = this.letters.toLowerCase(Locale.ROOT); String phaseLetters = this.lettersLower.replace('_',
62
     * '-'); if (phaseLetters.startsWith("pre")) { this.prePhase = true; int preLength = 3; if
63
     * (phaseLetters.startsWith("pre-")) { preLength = 4; } phaseLetters = phaseLetters.substring(preLength); } else {
64
     * this.prePhase = false; } this.phase = VersionPhase.of(phaseLetters);
65
     */
66
    this.digits = digits;
3✔
67
    if (this.digits.isEmpty()) {
4✔
68
      this.number = -1;
4✔
69
    } else {
70
      this.number = Integer.parseInt(this.digits);
5✔
71
    }
72
    if (EMPTY != null) {
2✔
73
      assert (!this.letters.isEmpty() || !this.digits.isEmpty() || !this.separator.isEmpty()
15✔
74
          || !this.pattern.isEmpty());
2!
75
    }
76
  }
1✔
77

78
  /**
79
   * @return the separator {@link String} (e.g. "." or "-") or the empty {@link String} ("") for none.
80
   */
81
  public String getSeparator() {
82

83
    return this.separator;
3✔
84
  }
85

86
  /**
87
   * @return the letters or the empty {@link String} ("") for none. In canonical {@link VersionIdentifier}s letters indicate the development phase (e.g. "pre",
88
   *     "rc", "alpha", "beta", "milestone", "test", "dev", "SNAPSHOT", etc.). However, letters are technically any
89
   *     {@link Character#isLetter(char) letter characters} and may also be something like a code-name (e.g. "Cupcake", "Donut", "Eclair", "Froyo",
90
   *     "Gingerbread", "Honeycomb", "Ice Cream Sandwich", "Jelly Bean" in case of Android internals). Please note that in such case it is impossible to
91
   *     properly decide which version is greater than another versions. To avoid mistakes, the comparison supports a strict mode that will let the comparison
92
   *     fail in such case. However, by default (e.g. for {@link Comparable#compareTo(Object)}) the default {@link String#compareTo(String) string comparison}
93
   *     (lexicographical) is used to ensure a natural order.
94
   * @see #getPhase()
95
   */
96
  public String getLettersString() {
97

98
    return this.letters.getLetters();
4✔
99
  }
100

101
  /**
102
   * @return the {@link VersionLetters}.
103
   */
104
  public VersionLetters getLetters() {
105

106
    return this.letters;
3✔
107
  }
108

109
  /**
110
   * @return the {@link VersionPhase} for the {@link #getLettersString() letters}. Will be {@link VersionPhase#UNDEFINED} if unknown and hence never
111
   *     {@code null}.
112
   * @see #getLettersString()
113
   */
114
  public VersionPhase getPhase() {
115

116
    return this.letters.getPhase();
4✔
117
  }
118

119
  /**
120
   * @return the digits or the empty {@link String} ("") for none. This is the actual {@link #getNumber() number} part of this {@link VersionSegment}. So the
121
   *     {@link VersionIdentifier} "1.0.001" will have three segments: The first one with "1" as digits, the second with "0" as digits, and a third with "001"
122
   *     as digits. You can get the same value via {@link #getNumber()} but this {@link String} representation will preserve leading zeros.
123
   */
124
  public String getDigits() {
125

126
    return this.digits;
3✔
127
  }
128

129
  /**
130
   * @return the {@link #getDigits() digits} and integer number. Will be {@code -1} if no {@link #getDigits() digits} are present.
131
   */
132
  public int getNumber() {
133

134
    return this.number;
3✔
135
  }
136

137
  /**
138
   * @return the potential pattern that is {@link #PATTERN_MATCH_ANY_STABLE_VERSION}, {@link #PATTERN_MATCH_ANY_VERSION}, or for no pattern the empty
139
   *     {@link String}.
140
   */
141
  public String getPattern() {
142

143
    return this.pattern;
3✔
144
  }
145

146
  /**
147
   * @return {@code true} if {@link #getPattern() pattern} is NOT {@link String#isEmpty() empty}.
148
   */
149
  public boolean isPattern() {
150

151
    return !this.pattern.isEmpty();
8✔
152
  }
153

154
  /**
155
   * @return the next {@link VersionSegment} or {@code null} if this is the tail of the {@link VersionIdentifier}.
156
   */
157
  public VersionSegment getNextOrNull() {
158

159
    return this.next;
3✔
160
  }
161

162
  /**
163
   * @return the next {@link VersionSegment} or the {@link #ofEmpty() empty segment} if this is the tail of the {@link VersionIdentifier}.
164
   */
165
  public VersionSegment getNextOrEmpty() {
166

167
    if (this.next == null) {
3✔
168
      return EMPTY;
2✔
169
    }
170
    return this.next;
3✔
171
  }
172

173
  /**
174
   * @return {@code true} if this is the empty {@link VersionSegment}, {@code false} otherwise.
175
   */
176
  public boolean isEmpty() {
177

178
    return (this == EMPTY);
7✔
179
  }
180

181
  /**
182
   * A valid {@link VersionSegment} has to meet the following requirements:
183
   * <ul>
184
   * <li>The {@link #getSeparator() separator} may not be {@link String#length() longer} than a single character (e.g.
185
   * ".-_1" or "--1" are not considered valid).</li>
186
   * <li>The {@link #getSeparator() separator} may only contain the characters '.', '-', or '_' (e.g. " 1" or "รถ1" are
187
   * not considered valid).</li>
188
   * <li>The combination of {@link #getPhase() phase} and {@link #getNumber() number} has to be
189
   * {@link VersionPhase#isValid(int) valid} (e.g. "pineapple-pen1" or "donut" are not considered valid).</li>
190
   * </ul>
191
   */
192
  @Override
193
  public boolean isValid() {
194

195
    if (!this.pattern.isEmpty()) {
4✔
196
      return false;
2✔
197
    }
198
    int separatorLen = this.separator.length();
4✔
199
    if (separatorLen > 1) {
3✔
200
      return false;
2✔
201
    } else if (separatorLen == 1) {
3✔
202
      if (!CharCategory.isValidSeparator(this.separator.charAt(0))) {
6✔
203
        return false;
2✔
204
      }
205
    }
206
    return this.letters.getPhase().isValid(this.number);
7✔
207
  }
208

209
  @Override
210
  public VersionComparisonResult compareVersion(VersionSegment other) {
211

212
    if (other == null) {
2!
213
      return VersionComparisonResult.GREATER_UNSAFE;
×
214
    }
215
    VersionComparisonResult lettersResult = this.letters.compareVersion(other.letters);
6✔
216
    if (!lettersResult.isEqual()) {
3✔
217
      return lettersResult;
2✔
218
    }
219
    if (!"_".equals(this.separator) && "_".equals(other.separator)) {
10✔
220
      if ("".equals(this.separator)) {
5!
221
        return VersionComparisonResult.LESS;
×
222
      } else {
223
        return VersionComparisonResult.GREATER;
2✔
224
      }
225
    } else if ("_".equals(this.separator) && !"_".equals(other.separator)) {
10!
226
      if ("".equals(other.separator)) {
5✔
227
        return VersionComparisonResult.GREATER;
2✔
228
      } else {
229
        return VersionComparisonResult.LESS;
2✔
230
      }
231
    }
232

233
    if (this.number != other.number) {
5✔
234
      if ((this.number < 0) && isPattern()) {
6✔
235
        return VersionComparisonResult.LESS_UNSAFE;
2✔
236
      } else if ((other.number < 0) && other.isPattern()) {
6!
237
        return VersionComparisonResult.GREATER_UNSAFE;
×
238
      } else if (this.number < other.number) {
5✔
239
        return VersionComparisonResult.LESS;
2✔
240
      } else {
241
        return VersionComparisonResult.GREATER;
2✔
242
      }
243
    } else if (this.separator.equals(other.separator)) {
6✔
244
      return VersionComparisonResult.EQUAL;
2✔
245
    } else {
246
      return VersionComparisonResult.EQUAL_UNSAFE;
2✔
247
    }
248
  }
249

250
  /**
251
   * Matches a {@link VersionSegment} with a potential {@link #getPattern() pattern} against another {@link VersionSegment}. This operation may not always be
252
   * symmetric.
253
   *
254
   * @param other the {@link VersionSegment} to match against.
255
   * @return the {@link VersionMatchResult} of the match.
256
   */
257
  public VersionMatchResult matches(VersionSegment other) {
258

259
    if (other == null) {
2!
260
      return VersionMatchResult.MISMATCH;
×
261
    }
262
    if (isEmpty() && other.isEmpty()) {
6!
263
      return VersionMatchResult.MATCH;
2✔
264
    }
265
    boolean isPattern = isPattern();
3✔
266
    if (isPattern) {
2✔
267
      if (!this.digits.isEmpty()) {
4✔
268
        if (this.number != other.number) {
5✔
269
          return VersionMatchResult.MISMATCH;
2✔
270
        }
271
      }
272
      if (!this.separator.isEmpty()) {
4✔
273
        if (!this.separator.equals(other.separator)) {
6✔
274
          return VersionMatchResult.MISMATCH;
2✔
275
        }
276
      }
277
    } else {
278
      if ((this.number != other.number) || !this.separator.equals(other.separator)) {
11!
279
        return VersionMatchResult.MISMATCH;
2✔
280
      }
281
    }
282
    VersionMatchResult result = this.letters.matches(other.letters, isPattern);
7✔
283
    if (isPattern && (result == VersionMatchResult.EQUAL)) {
5!
284
      if (this.pattern.equals(PATTERN_MATCH_ANY_STABLE_VERSION)) {
5✔
285
        VersionLetters developmentPhase = other.getDevelopmentPhase();
3✔
286
        if (developmentPhase.isUnstable()) {
3✔
287
          return VersionMatchResult.MISMATCH;
2✔
288
        }
289
        return VersionMatchResult.MATCH;
2✔
290
      } else if (this.pattern.equals(PATTERN_MATCH_ANY_VERSION)) {
5!
291
        return VersionMatchResult.MATCH;
2✔
292
      } else {
293
        throw new IllegalStateException("Pattern=" + this.pattern);
×
294
      }
295
    }
296
    return result;
2✔
297
  }
298

299
  /**
300
   * @return the {@link VersionLetters} that represent a {@link VersionLetters#isDevelopmentPhase() development phase} searching from this
301
   * {@link VersionSegment} to all {@link #getNextOrNull() next segments}. Will be {@link VersionPhase#NONE} if no
302
   * {@link VersionPhase#isDevelopmentPhase() development phase} was found and {@link VersionPhase#UNDEFINED} if multiple
303
   * {@link VersionPhase#isDevelopmentPhase() development phase}s have been found.
304
   * @see VersionIdentifier#getDevelopmentPhase()
305
   */
306
  protected VersionLetters getDevelopmentPhase() {
307

308
    VersionLetters result = VersionLetters.EMPTY;
2✔
309
    VersionSegment segment = this;
2✔
310
    while (segment != null) {
2✔
311
      if (segment.letters.isDevelopmentPhase()) {
4✔
312
        if (result == VersionLetters.EMPTY) {
3!
313
          result = segment.letters;
4✔
314
        } else {
315
          result = VersionLetters.UNDEFINED;
×
316
        }
317
      }
318
      segment = segment.next;
4✔
319
    }
320
    return result;
2✔
321
  }
322

323
  @Override
324
  public boolean equals(Object obj) {
325

326
    if (obj == this) {
3!
327
      return true;
×
328
    } else if (!(obj instanceof VersionSegment)) {
3✔
329
      return false;
2✔
330
    }
331
    VersionSegment other = (VersionSegment) obj;
3✔
332
    if (!Objects.equals(this.digits, other.digits)) {
6✔
333
      return false;
2✔
334
    } else if (!Objects.equals(this.separator, other.separator)) {
6!
335
      return false;
×
336
    } else if (!Objects.equals(this.letters, other.letters)) {
6!
337
      return false;
×
338
    } else if (!Objects.equals(this.pattern, other.pattern)) {
6!
339
      return false;
×
340
    } else if (!Objects.equals(this.next, other.next)) {
6✔
341
      return false;
2✔
342
    }
343
    return true;
2✔
344
  }
345

346
  @Override
347
  public String toString() {
348

349
    return this.separator + this.letters + this.digits + this.pattern;
11✔
350
  }
351

352
  /**
353
   * @return the {@link #isEmpty() empty} {@link VersionSegment} instance.
354
   */
355
  public static VersionSegment ofEmpty() {
356

357
    return EMPTY;
2✔
358
  }
359

360
  static VersionSegment of(String version) {
361

362
    CharReader reader = new CharReader(version);
5✔
363
    VersionSegment start = null;
2✔
364
    VersionSegment current = null;
2✔
365
    while (reader.hasNext()) {
3✔
366
      VersionSegment segment = parseSegment(reader);
3✔
367
      if (current == null) {
2✔
368
        start = segment;
3✔
369
      } else {
370
        current.next = segment;
3✔
371
      }
372
      current = segment;
2✔
373
    }
1✔
374
    return start;
2✔
375
  }
376

377
  private static VersionSegment parseSegment(CharReader reader) {
378

379
    String separator = reader.readSeparator();
3✔
380
    String letters = reader.readLetters();
3✔
381
    String digits = reader.readDigits();
3✔
382
    String pattern = reader.readPattern();
3✔
383
    return new VersionSegment(separator, letters, digits, pattern);
8✔
384
  }
385

386
}
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

© 2025 Coveralls, Inc