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

devonfw / IDEasy / 30882542201

04 Aug 2026 06:01AM UTC coverage: 72.616% (+0.03%) from 72.587%
30882542201

Pull #2246

github

web-flow
Merge 33bd8f4a3 into cd9770b9c
Pull Request #2246: #1965: improve dependent installation

5017 of 7657 branches covered (65.52%)

Branch coverage included in aggregate %.

13087 of 17274 relevant lines covered (75.76%)

3.21 hits per line

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

75.17
cli/src/main/java/com/devonfw/tools/ide/tool/ToolInstallRequest.java
1
package com.devonfw.tools.ide.tool;
2

3
import java.nio.file.Path;
4

5
import org.slf4j.Logger;
6
import org.slf4j.LoggerFactory;
7

8
import com.devonfw.tools.ide.process.ProcessContext;
9
import com.devonfw.tools.ide.step.Step;
10
import com.devonfw.tools.ide.version.GenericVersionRange;
11
import com.devonfw.tools.ide.version.VersionIdentifier;
12
import com.devonfw.tools.ide.version.VersionRange;
13

14
/**
15
 * Container for data related to a tool installation.
16
 */
17
public final class ToolInstallRequest {
18

19
  private static final Logger LOG = LoggerFactory.getLogger(ToolInstallRequest.class);
4✔
20

21
  private final PackageManagerRequest parentPackageManagerRequest;
22

23
  private final ToolInstallRequest parentToolInstallRequest;
24

25
  private final boolean silent;
26

27
  private final boolean direct;
28

29
  private boolean cveCheckDone;
30

31
  private ToolEditionAndVersion requested;
32

33
  private ToolEditionAndVersion installed;
34

35
  private ProcessContext processContext;
36

37
  private Path toolPath;
38

39
  private boolean extraInstallation;
40

41
  private boolean ignoreProject;
42

43
  private Step step;
44

45
  /**
46
   * The constructor.
47
   *
48
   * @param silent the {@link #isSilent() silent} flag.
49
   */
50
  public ToolInstallRequest(boolean silent) {
51
    this((ToolInstallRequest) null, silent, false);
6✔
52
  }
1✔
53

54
  /**
55
   * The constructor.
56
   *
57
   * @param parent the parent {@link ToolInstallRequest} (in case of a dependency).
58
   */
59
  public ToolInstallRequest(ToolInstallRequest parent) {
60
    this(parent, parent.silent, false);
6✔
61
  }
1✔
62

63
  /**
64
   * The constructor.
65
   *
66
   * @param parent the parent {@link PackageManagerRequest} (in case of a dependency).
67
   */
68
  public ToolInstallRequest(PackageManagerRequest parent) {
69
    this(parent, false, false);
5✔
70
  }
1✔
71

72
  /**
73
   * The constructor.
74
   *
75
   * @param silent the {@link #isSilent() silent} flag.
76
   * @param direct the {@link #isDirect() direct} flag.
77
   */
78
  private ToolInstallRequest(ToolInstallRequest parent, boolean silent, boolean direct) {
79
    super();
2✔
80
    this.parentToolInstallRequest = parent;
3✔
81
    this.parentPackageManagerRequest = null;
3✔
82
    this.silent = silent;
3✔
83
    this.direct = direct;
3✔
84
    if (parent != null) {
2✔
85
      this.processContext = parent.processContext;
4✔
86
      this.ignoreProject = parent.ignoreProject;
4✔
87
    }
88
  }
1✔
89

90
  private ToolInstallRequest(PackageManagerRequest parent, boolean silent, boolean direct) {
91
    super();
2✔
92
    this.parentPackageManagerRequest = parent;
3✔
93
    this.parentToolInstallRequest = null;
3✔
94
    this.silent = silent;
3✔
95
    this.direct = direct;
3✔
96
    if (parent != null) {
2!
97
      this.processContext = parent.getProcessContext();
4✔
98
    }
99
  }
1✔
100

101
  /**
102
   * @return {@code true} if an installation loop was found and logged, {@code false} otherwise.
103
   */
104
  public boolean isInstallLoop() {
105

106
    if ((this.requested == null) || (this.requested.getEdition() == null)) {
7!
107
      throw new IllegalStateException(); // this method was called too early
×
108
    }
109
    StringBuilder sb = new StringBuilder();
4✔
110
    boolean loopFound = detectInstallLoopRecursively(this.requested, sb);
6✔
111
    if (loopFound) {
2!
112
      LOG.warn("Found installation loop:\n"
×
113
              + "{}\n"
114
              + "This typically indicates an internal bug in IDEasy.\n"
115
              + "Please report this bug, when you see this and include this entire warning message.\n"
116
              + "We are now trying to prevent an infinity loop and abort the recursive installation.",
117
          sb);
118
    }
119
    return loopFound;
2✔
120
  }
121

122
  private boolean detectInstallLoopRecursively(ToolEditionAndVersion toolEditionAndVersion, StringBuilder sb) {
123

124
    if (this.requested != toolEditionAndVersion) {
4✔
125
      if (this.requested.getEdition().equals(toolEditionAndVersion.getEdition())) {
7!
126
        if (this.requested.getResolvedVersion().equals(toolEditionAndVersion.getResolvedVersion())) {
×
127
          sb.append(this.requested);
×
128
          return true;
×
129
        }
130
      }
131
    }
132

133
    boolean loopFound;
134

135
    if (this.parentToolInstallRequest != null) {
3✔
136
      loopFound = this.parentToolInstallRequest.detectInstallLoopRecursively(toolEditionAndVersion, sb);
7✔
137
    } else if (this.parentPackageManagerRequest != null) {
3✔
138
      if (this.parentPackageManagerRequest.getType().equals("uninstall")) {
6✔
139
        return false;
2✔
140
      }
141
      loopFound = this.parentPackageManagerRequest.getToolInstallRequest().detectInstallLoopRecursively(toolEditionAndVersion, sb);
8✔
142
    } else {
143
      return false;
2✔
144
    }
145
    if (loopFound && (sb != null)) {
2!
146
      sb.append("-->");
×
147
      sb.append(this.requested);
×
148
    }
149
    return loopFound;
2✔
150
  }
151

152
  /**
153
   * @return {@code true} if this installation should be silent and log information like "tool already installed" only on debug level to avoid spam,
154
   *     {@code false} otherwise. A {@link #isDirect() direct} installation should never be silent.
155
   */
156
  public boolean isSilent() {
157

158
    return this.silent;
3✔
159
  }
160

161
  /**
162
   * @return {@code true} if the user directly triggered this tool installation (via "ide install tool"), {@code false} otherwise (indirect installation e.g. as
163
   *     dependency or via "ide create" or "ide update").
164
   */
165
  public boolean isDirect() {
166

167
    return this.direct;
×
168
  }
169

170
  /**
171
   * @return {@code true} if CVEs have already been checked, {@code false} otherwise.
172
   */
173
  public boolean isCveCheckDone() {
174

175
    return this.cveCheckDone;
3✔
176
  }
177

178
  void setCveCheckDone() {
179

180
    assert !this.cveCheckDone;
4!
181
    this.cveCheckDone = true;
3✔
182
  }
1✔
183

184
  /**
185
   * @return the {@link ToolEditionAndVersion} that is requested to be installed.
186
   */
187
  public ToolEditionAndVersion getRequested() {
188

189
    return this.requested;
3✔
190
  }
191

192
  /**
193
   * @param requested new value of {@link #getRequested()}.
194
   */
195
  public void setRequested(ToolEditionAndVersion requested) {
196
    if (this.requested != null) {
3!
197
      throw new IllegalStateException();
×
198
    }
199
    this.requested = requested;
3✔
200
  }
1✔
201

202
  /**
203
   * @return the {@link ToolEditionAndVersion} that is currently installed or {@code null} if the tool is not installed yet.
204
   */
205
  public ToolEditionAndVersion getInstalled() {
206

207
    return this.installed;
3✔
208
  }
209

210
  /**
211
   * @param installed new value of {@link #getInstalled()}.
212
   */
213
  public void setInstalled(ToolEditionAndVersion installed) {
214

215
    if (this.installed != null) {
3!
216
      throw new IllegalStateException();
×
217
    }
218
    this.installed = installed;
3✔
219
  }
1✔
220

221
  /**
222
   * @return the {@link ProcessContext} to use for executing the tool. Will also be configured during the installation (variables set, PATH extended).
223
   */
224
  public ProcessContext getProcessContext() {
225
    return this.processContext;
3✔
226
  }
227

228
  /**
229
   * @param processContext new value of {@link #getProcessContext()}.
230
   */
231
  public void setProcessContext(ProcessContext processContext) {
232

233
    if (this.processContext != null) {
3!
234
      throw new IllegalStateException();
×
235
    }
236
    this.processContext = processContext;
3✔
237
  }
1✔
238

239
  /**
240
   * @return the {@link Path} inside the project where the tool installation should be linked to.
241
   */
242
  public Path getToolPath() {
243

244
    return this.toolPath;
3✔
245
  }
246

247
  /**
248
   * @param toolPath new value of {@link #getToolPath()}.
249
   */
250
  public void setToolPath(Path toolPath) {
251

252
    if (this.toolPath != null) {
3!
253
      throw new IllegalStateException();
×
254
    }
255
    this.toolPath = toolPath;
3✔
256
  }
1✔
257

258
  /**
259
   * Called to trigger an extra installation with a custom tool path.
260
   *
261
   * @param toolPath new value of {@link #getToolPath()}.
262
   */
263
  public void setToolPathForExtraInstallation(Path toolPath) {
264

265
    setToolPath(toolPath);
3✔
266
    this.extraInstallation = true;
3✔
267
  }
1✔
268

269
  /**
270
   * @return {@code true} if this is an extra installation, {@code false} otherwise (standard installation).
271
   */
272
  public boolean isExtraInstallation() {
273

274
    return this.extraInstallation;
3✔
275
  }
276

277
  /**
278
   * @return {@code true} if the current project should be ignored during tool installation, {@code false} otherwise.
279
   */
280
  public boolean isIgnoreProject() {
281

282
    return this.ignoreProject;
3✔
283
  }
284

285
  /**
286
   * @param ignoreProject new value of {@link #isIgnoreProject() ignoreProject}.
287
   */
288
  public void setIgnoreProject(boolean ignoreProject) {
289

290
    this.ignoreProject = ignoreProject;
3✔
291
  }
1✔
292

293
  /**
294
   * @return the {@link Step} for the installation.
295
   */
296
  public Step getStep() {
297

298
    return this.step;
3✔
299
  }
300

301
  /**
302
   * @param step new value of {@link #getStep()}.
303
   */
304
  public void setStep(Step step) {
305

306
    if (this.step != null) {
×
307
      throw new IllegalStateException();
×
308
    }
309
    this.step = step;
×
310
  }
×
311

312
  /**
313
   * @return {@code true} if an additional installation is required not linked to the project's software folder (e.g. because of a transitive installation from
314
   *     a dependency that is incompatible with the project version), {@code false} otherwise.
315
   */
316
  public boolean isAdditionalInstallation() {
317
    if (this.requested != null) {
3!
318
      GenericVersionRange versionToInstall = this.requested.getVersion();
4✔
319
      return (versionToInstall instanceof VersionRange);
3✔
320
    }
321
    return false;
×
322
  }
323

324
  /**
325
   * @return {@code true} if the {@link #getRequested() requested edition and version} matches the {@link #getInstalled() installed edition and version}
326
   */
327
  public boolean isAlreadyInstalled() {
328

329
    if (this.installed == null) {
3✔
330
      return false;
2✔
331
    }
332
    VersionIdentifier installedVersion = this.installed.getResolvedVersion();
4✔
333
    if (installedVersion == null) {
2✔
334
      return false;
2✔
335
    }
336
    ToolEdition installedEdition = this.installed.getEdition();
4✔
337
    if (installedEdition == null) {
2!
338
      return false; // should actually never happen
×
339
    }
340
    if (!this.requested.getEdition().equals(installedEdition)) {
6✔
341
      return false;
2✔
342
    }
343
    return installedVersion.equals(this.requested.getResolvedVersion());
6✔
344
  }
345

346
  /**
347
   * @return a new {@link #isDirect() direct} {@link ToolInstallRequest}.
348
   */
349
  public static ToolInstallRequest ofDirect() {
350

351
    return new ToolInstallRequest((ToolInstallRequest) null, false, true);
8✔
352
  }
353
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc