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

devonfw / IDEasy / 16770776823

06 Aug 2025 07:45AM UTC coverage: 68.94% (-0.4%) from 69.373%
16770776823

Pull #1440

github

web-flow
Merge 58a7d5dce into d13807b57
Pull Request #1440: Private gha

3341 of 5302 branches covered (63.01%)

Branch coverage included in aggregate %.

8627 of 12058 relevant lines covered (71.55%)

3.15 hits per line

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

86.47
cli/src/main/java/com/devonfw/tools/ide/version/VersionIdentifier.java
1
package com.devonfw.tools.ide.version;
2

3
import java.util.List;
4
import java.util.Objects;
5

6
import com.devonfw.tools.ide.cli.CliException;
7
import com.devonfw.tools.ide.log.IdeLogger;
8
import com.devonfw.tools.ide.tool.ToolCommandlet;
9
import com.fasterxml.jackson.annotation.JsonCreator;
10
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
11

12
/**
13
 * Data-type to represent a {@link VersionIdentifier} in a structured way and allowing {@link #compareVersion(VersionIdentifier) comparison} of
14
 * {@link VersionIdentifier}s.
15
 */
16
public final class VersionIdentifier implements VersionObject<VersionIdentifier>, GenericVersionRange {
17

18
  /** {@link VersionIdentifier} "*" that will resolve to the latest stable version. */
19
  public static final VersionIdentifier LATEST = VersionIdentifier.of("*");
3✔
20

21
  /** {@link VersionIdentifier} "*!" that will resolve to the latest snapshot. */
22
  public static final VersionIdentifier LATEST_UNSTABLE = VersionIdentifier.of("*!");
4✔
23

24
  private final VersionSegment start;
25

26
  private final VersionLetters developmentPhase;
27

28
  private final boolean valid;
29

30
  private VersionIdentifier(VersionSegment start) {
31

32
    super();
2✔
33
    Objects.requireNonNull(start);
3✔
34
    this.start = start;
3✔
35
    boolean isValid = this.start.getSeparator().isEmpty() && this.start.getLettersString().isEmpty();
14✔
36
    boolean hasPositiveNumber = false;
2✔
37
    VersionLetters dev = VersionLetters.EMPTY;
2✔
38
    VersionSegment segment = this.start;
3✔
39
    while (segment != null) {
2✔
40
      if (!segment.isValid()) {
3✔
41
        isValid = false;
3✔
42
      } else if (segment.getNumber() > 0) {
3✔
43
        hasPositiveNumber = true;
2✔
44
      }
45
      VersionLetters segmentLetters = segment.getLetters();
3✔
46
      if (segmentLetters.isDevelopmentPhase()) {
3✔
47
        if (dev.isEmpty()) {
3✔
48
          dev = segmentLetters;
3✔
49
        } else {
50
          dev = VersionLetters.UNDEFINED;
2✔
51
          isValid = false;
2✔
52
        }
53
      }
54
      segment = segment.getNextOrNull();
3✔
55
    }
1✔
56
    this.developmentPhase = dev;
3✔
57
    this.valid = isValid && hasPositiveNumber;
9✔
58
  }
1✔
59

60
  /**
61
   * Resolves a version pattern against a list of available versions.
62
   *
63
   * @param version the version pattern to resolve
64
   * @param versions the
65
   *     {@link com.devonfw.tools.ide.tool.repository.ToolRepository#getSortedVersions(String, String, ToolCommandlet) available versions, sorted in descending
66
   *     order}.
67
   * @param logger the {@link IdeLogger}.
68
   * @return the resolved version
69
   */
70
  public static VersionIdentifier resolveVersionPattern(GenericVersionRange version, List<VersionIdentifier> versions, IdeLogger logger) {
71
    if (version == null) {
2!
72
      version = LATEST;
×
73
    }
74
    if (!version.isPattern()) {
3✔
75
      return (VersionIdentifier) version;
3✔
76
    }
77
    for (VersionIdentifier vi : versions) {
10✔
78
      if (version.contains(vi)) {
4!
79
        logger.debug("Resolved version pattern {} to version {}", version, vi);
13✔
80
        return vi;
2✔
81
      }
82
    }
×
83
    throw new CliException(
5✔
84
        "Could not find any version matching '" + version + "' - there are " + versions.size() + " version(s) available but none matched!");
4✔
85
  }
86

87
  /**
88
   * @return the first {@link VersionSegment} of this {@link VersionIdentifier}. To get other segments use {@link VersionSegment#getNextOrEmpty()} or
89
   *     {@link VersionSegment#getNextOrNull()}.
90
   */
91
  public VersionSegment getStart() {
92

93
    return this.start;
3✔
94
  }
95

96
  /**
97
   * A valid {@link VersionIdentifier} has to meet the following requirements:
98
   * <ul>
99
   * <li>All {@link VersionSegment segments} themselves are {@link VersionSegment#isValid() valid}.</li>
100
   * <li>The {@link #getStart() start} {@link VersionSegment segment} shall have an {@link String#isEmpty() empty}
101
   * {@link VersionSegment#getSeparator() separator} (e.g. ".1.0" or "-1-2" are not considered valid).</li>
102
   * <li>The {@link #getStart() start} {@link VersionSegment segment} shall have an {@link String#isEmpty() empty}
103
   * {@link VersionSegment#getLettersString() letter-sequence} (e.g. "RC1" or "beta" are not considered valid).</li>
104
   * <li>Have at least one {@link VersionSegment segment} with a positive {@link VersionSegment#getNumber() number}
105
   * (e.g. "0.0.0" or "0.alpha" are not considered valid).</li>
106
   * <li>Have at max one {@link VersionSegment segment} with a {@link VersionSegment#getPhase() phase} that is a real
107
   * {@link VersionPhase#isDevelopmentPhase() development phase} (e.g. "1.alpha1.beta2" or "1.0.rc1-milestone2" are not
108
   * considered valid).</li>
109
   * <li>It is NOT a {@link #isPattern() pattern}.</li>
110
   * </ul>
111
   */
112
  @Override
113
  public boolean isValid() {
114

115
    return this.valid;
3✔
116
  }
117

118
  @Override
119
  public boolean isPattern() {
120

121
    VersionSegment segment = this.start;
3✔
122
    while (segment != null) {
2✔
123
      if (segment.isPattern()) {
3✔
124
        return true;
2✔
125
      }
126
      segment = segment.getNextOrNull();
4✔
127
    }
128
    return false;
2✔
129
  }
130

131
  /**
132
   * @return the {@link VersionLetters#isDevelopmentPhase() development phase} of this {@link VersionIdentifier}. Will be {@link VersionLetters#EMPTY} if no
133
   *     development phase is specified in any {@link VersionSegment} and will be {@link VersionLetters#UNDEFINED} if more than one
134
   *     {@link VersionLetters#isDevelopmentPhase() development phase} is specified (e.g. "1.0-alpha1.rc2").
135
   */
136
  public VersionLetters getDevelopmentPhase() {
137

138
    return this.developmentPhase;
3✔
139
  }
140

141
  @Override
142
  public VersionComparisonResult compareVersion(VersionIdentifier other) {
143

144
    if (other == null) {
2!
145
      return VersionComparisonResult.GREATER_UNSAFE;
×
146
    }
147
    VersionSegment thisSegment = this.start;
3✔
148
    VersionSegment otherSegment = other.start;
3✔
149
    VersionComparisonResult result = null;
2✔
150
    boolean unsafe = false;
2✔
151
    boolean todo = true;
2✔
152
    do {
153
      result = thisSegment.compareVersion(otherSegment);
4✔
154
      if (result.isEqual()) {
3✔
155
        if (thisSegment.isEmpty() && otherSegment.isEmpty()) {
6!
156
          todo = false;
3✔
157
        } else if (result.isUnsafe()) {
3!
158
          unsafe = true;
×
159
        }
160
      } else {
161
        todo = false;
2✔
162
      }
163
      thisSegment = thisSegment.getNextOrEmpty();
3✔
164
      otherSegment = otherSegment.getNextOrEmpty();
3✔
165
    } while (todo);
2✔
166
    if (unsafe) {
2!
167
      return result.withUnsafe();
×
168
    }
169
    return result;
2✔
170
  }
171

172
  /**
173
   * @param other the {@link VersionIdentifier} to be matched.
174
   * @return {@code true} if this {@link VersionIdentifier} is equal to the given {@link VersionIdentifier} or this {@link VersionIdentifier} is a pattern
175
   *     version (e.g. "17*" or "17.*") and the given {@link VersionIdentifier} matches to that pattern.
176
   */
177
  public boolean matches(VersionIdentifier other) {
178

179
    if (other == null) {
2✔
180
      return false;
2✔
181
    }
182
    VersionSegment thisSegment = this.start;
3✔
183
    VersionSegment otherSegment = other.start;
3✔
184
    while (true) {
185
      VersionMatchResult matchResult = thisSegment.matches(otherSegment);
4✔
186
      if (matchResult == VersionMatchResult.MATCH) {
3✔
187
        return true;
2✔
188
      } else if (matchResult == VersionMatchResult.MISMATCH) {
3✔
189
        return false;
2✔
190
      }
191
      thisSegment = thisSegment.getNextOrEmpty();
3✔
192
      otherSegment = otherSegment.getNextOrEmpty();
3✔
193
    }
1✔
194
  }
195

196
  @Override
197
  public VersionIdentifier getMin() {
198

199
    return this;
×
200
  }
201

202
  @Override
203
  public VersionIdentifier getMax() {
204

205
    return this;
×
206
  }
207

208
  @Override
209
  public boolean contains(VersionIdentifier version) {
210

211
    return matches(version);
4✔
212
  }
213

214
  @Override
215
  public int hashCode() {
216

217
    VersionSegment segment = this.start;
×
218
    int hash = 1;
×
219
    while (segment != null) {
×
220
      hash = hash * 31 + segment.hashCode();
×
221
      segment = segment.getNextOrNull();
×
222
    }
223
    return hash;
×
224
  }
225

226
  @Override
227
  public boolean equals(Object obj) {
228

229
    if (obj == this) {
3✔
230
      return true;
2✔
231
    } else if (!(obj instanceof VersionIdentifier)) {
3✔
232
      return false;
2✔
233
    }
234
    VersionIdentifier other = (VersionIdentifier) obj;
3✔
235
    return Objects.equals(this.start, other.start);
6✔
236
  }
237

238
  @Override
239
  @JsonSerialize
240
  public String toString() {
241

242
    StringBuilder sb = new StringBuilder();
4✔
243
    VersionSegment segment = this.start;
3✔
244
    while (segment != null) {
2✔
245
      sb.append(segment.toString());
5✔
246
      segment = segment.getNextOrNull();
4✔
247
    }
248
    return sb.toString();
3✔
249
  }
250

251
  /**
252
   * @param version the {@link #toString() string representation} of the {@link VersionIdentifier} to parse.
253
   * @return the parsed {@link VersionIdentifier}.
254
   */
255
  @JsonCreator
256
  public static VersionIdentifier of(String version) {
257

258
    if (version == null) {
2✔
259
      return null;
2✔
260
    } else if (version.equals("latest")) {
4!
261
      return VersionIdentifier.LATEST;
×
262
    }
263
    VersionSegment startSegment = VersionSegment.of(version);
3✔
264
    if (startSegment == null) {
2✔
265
      return null;
2✔
266
    }
267
    return new VersionIdentifier(startSegment);
5✔
268
  }
269

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