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

devonfw / IDEasy / 16914988730

12 Aug 2025 04:34PM UTC coverage: 69.51% (+0.01%) from 69.496%
16914988730

push

github

web-flow
#1443: Correctly validate tool version before setting and installing (#1446)

Co-authored-by: Jörg Hohwiller <hohwille@users.noreply.github.com>

3369 of 5289 branches covered (63.7%)

Branch coverage included in aggregate %.

8748 of 12143 relevant lines covered (72.04%)

3.17 hits per line

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

86.52
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

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

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

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

22
  private final VersionSegment start;
23

24
  private final VersionLetters developmentPhase;
25

26
  private final boolean valid;
27

28
  private VersionIdentifier(VersionSegment start) {
29

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

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

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

96
    return this.start;
3✔
97
  }
98

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

118
    return this.valid;
3✔
119
  }
120

121
  @Override
122
  public boolean isPattern() {
123

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

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

141
    return this.developmentPhase;
3✔
142
  }
143

144
  @Override
145
  public VersionComparisonResult compareVersion(VersionIdentifier other) {
146

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

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

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

199
  @Override
200
  public VersionIdentifier getMin() {
201

202
    return this;
×
203
  }
204

205
  @Override
206
  public VersionIdentifier getMax() {
207

208
    return this;
×
209
  }
210

211
  @Override
212
  public boolean contains(VersionIdentifier version) {
213

214
    return matches(version);
4✔
215
  }
216

217
  @Override
218
  public int hashCode() {
219

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

229
  @Override
230
  public boolean equals(Object obj) {
231

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

241
  @Override
242
  public String toString() {
243

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

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

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

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