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

devonfw / IDEasy / 14716891516

28 Apr 2025 08:09PM UTC coverage: 67.369% (-0.08%) from 67.448%
14716891516

Pull #1017

github

web-flow
Merge 314862506 into 232e6f193
Pull Request #1017: #404: enhance logging with custom slf4j bridge

3101 of 5006 branches covered (61.95%)

Branch coverage included in aggregate %.

8000 of 11472 relevant lines covered (69.74%)

3.04 hits per line

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

59.71
cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java
1
package com.devonfw.tools.ide.context;
2

3
import java.io.BufferedReader;
4
import java.io.InputStreamReader;
5
import java.net.URL;
6
import java.net.URLConnection;
7
import java.nio.file.Files;
8
import java.nio.file.Path;
9
import java.time.LocalDateTime;
10
import java.util.ArrayList;
11
import java.util.HashMap;
12
import java.util.Iterator;
13
import java.util.List;
14
import java.util.Locale;
15
import java.util.Map;
16
import java.util.Objects;
17

18
import com.devonfw.tools.ide.cli.CliAbortException;
19
import com.devonfw.tools.ide.cli.CliArgument;
20
import com.devonfw.tools.ide.cli.CliArguments;
21
import com.devonfw.tools.ide.cli.CliException;
22
import com.devonfw.tools.ide.commandlet.Commandlet;
23
import com.devonfw.tools.ide.commandlet.CommandletManager;
24
import com.devonfw.tools.ide.commandlet.CommandletManagerImpl;
25
import com.devonfw.tools.ide.commandlet.ContextCommandlet;
26
import com.devonfw.tools.ide.commandlet.EnvironmentCommandlet;
27
import com.devonfw.tools.ide.commandlet.HelpCommandlet;
28
import com.devonfw.tools.ide.common.SystemPath;
29
import com.devonfw.tools.ide.completion.CompletionCandidate;
30
import com.devonfw.tools.ide.completion.CompletionCandidateCollector;
31
import com.devonfw.tools.ide.completion.CompletionCandidateCollectorDefault;
32
import com.devonfw.tools.ide.environment.AbstractEnvironmentVariables;
33
import com.devonfw.tools.ide.environment.EnvironmentVariables;
34
import com.devonfw.tools.ide.environment.EnvironmentVariablesType;
35
import com.devonfw.tools.ide.environment.IdeSystem;
36
import com.devonfw.tools.ide.environment.IdeSystemImpl;
37
import com.devonfw.tools.ide.git.GitContext;
38
import com.devonfw.tools.ide.git.GitContextImpl;
39
import com.devonfw.tools.ide.git.GitUrl;
40
import com.devonfw.tools.ide.io.FileAccess;
41
import com.devonfw.tools.ide.io.FileAccessImpl;
42
import com.devonfw.tools.ide.log.IdeLogLevel;
43
import com.devonfw.tools.ide.log.IdeLogger;
44
import com.devonfw.tools.ide.log.IdeSubLogger;
45
import com.devonfw.tools.ide.merge.DirectoryMerger;
46
import com.devonfw.tools.ide.migration.IdeMigrator;
47
import com.devonfw.tools.ide.network.NetworkProxy;
48
import com.devonfw.tools.ide.os.SystemInfo;
49
import com.devonfw.tools.ide.os.SystemInfoImpl;
50
import com.devonfw.tools.ide.os.WindowsHelper;
51
import com.devonfw.tools.ide.os.WindowsHelperImpl;
52
import com.devonfw.tools.ide.os.WindowsPathSyntax;
53
import com.devonfw.tools.ide.process.ProcessContext;
54
import com.devonfw.tools.ide.process.ProcessContextImpl;
55
import com.devonfw.tools.ide.process.ProcessResult;
56
import com.devonfw.tools.ide.property.Property;
57
import com.devonfw.tools.ide.step.Step;
58
import com.devonfw.tools.ide.step.StepImpl;
59
import com.devonfw.tools.ide.tool.repository.CustomToolRepository;
60
import com.devonfw.tools.ide.tool.repository.CustomToolRepositoryImpl;
61
import com.devonfw.tools.ide.tool.repository.DefaultToolRepository;
62
import com.devonfw.tools.ide.tool.repository.MavenRepository;
63
import com.devonfw.tools.ide.tool.repository.ToolRepository;
64
import com.devonfw.tools.ide.url.model.UrlMetadata;
65
import com.devonfw.tools.ide.util.DateTimeUtil;
66
import com.devonfw.tools.ide.validation.ValidationResult;
67
import com.devonfw.tools.ide.validation.ValidationResultValid;
68
import com.devonfw.tools.ide.validation.ValidationState;
69
import com.devonfw.tools.ide.variable.IdeVariables;
70
import com.devonfw.tools.ide.version.VersionIdentifier;
71

72
/**
73
 * Abstract base implementation of {@link IdeContext}.
74
 */
75
public abstract class AbstractIdeContext implements IdeContext {
76

77
  private static final GitUrl IDE_URLS_GIT = new GitUrl("https://github.com/devonfw/ide-urls.git", null);
7✔
78

79
  private static final String LICENSE_URL = "https://github.com/devonfw/IDEasy/blob/main/documentation/LICENSE.adoc";
80

81
  private final IdeStartContextImpl startContext;
82

83
  private Path ideHome;
84

85
  private final Path ideRoot;
86

87
  private Path confPath;
88

89
  protected Path settingsPath;
90

91
  private Path settingsCommitIdPath;
92

93
  protected Path pluginsPath;
94

95
  private Path workspacePath;
96

97
  private String workspaceName;
98

99
  private Path cwd;
100

101
  private Path downloadPath;
102

103
  protected Path userHome;
104

105
  private Path userHomeIde;
106

107
  private SystemPath path;
108

109
  private WindowsPathSyntax pathSyntax;
110

111
  private final SystemInfo systemInfo;
112

113
  private EnvironmentVariables variables;
114

115
  private final FileAccess fileAccess;
116

117
  protected CommandletManager commandletManager;
118

119
  protected ToolRepository defaultToolRepository;
120

121
  private CustomToolRepository customToolRepository;
122

123
  private MavenRepository mavenRepository;
124

125
  private DirectoryMerger workspaceMerger;
126

127
  protected UrlMetadata urlMetadata;
128

129
  protected Path defaultExecutionDirectory;
130

131
  private StepImpl currentStep;
132

133
  protected Boolean online;
134

135
  protected IdeSystem system;
136

137
  private NetworkProxy networkProxy;
138

139
  private WindowsHelper windowsHelper;
140

141
  /** Context used for logging */
142
  public static IdeContext loggingContext;
143

144
  /**
145
   * The constructor.
146
   *
147
   * @param startContext the {@link IdeLogger}.
148
   * @param workingDirectory the optional {@link Path} to current working directory.
149
   */
150
  public AbstractIdeContext(IdeStartContextImpl startContext, Path workingDirectory) {
151

152
    super();
2✔
153
    this.startContext = startContext;
3✔
154
    this.systemInfo = SystemInfoImpl.INSTANCE;
3✔
155
    this.commandletManager = new CommandletManagerImpl(this);
6✔
156
    this.fileAccess = new FileAccessImpl(this);
6✔
157
    String workspace = WORKSPACE_MAIN;
2✔
158
    if (workingDirectory == null) {
2!
159
      workingDirectory = Path.of(System.getProperty("user.dir"));
×
160
    } else {
161
      workingDirectory = workingDirectory.toAbsolutePath();
3✔
162
    }
163
    this.cwd = workingDirectory;
3✔
164
    // detect IDE_HOME and WORKSPACE
165
    Path currentDir = workingDirectory;
2✔
166
    String name1 = "";
2✔
167
    String name2 = "";
2✔
168
    Path ideRootPath = getIdeRootPathFromEnv();
3✔
169
    while (currentDir != null) {
2✔
170
      trace("Looking for IDE_HOME in {}", currentDir);
9✔
171
      if (isIdeHome(currentDir)) {
4✔
172
        if (FOLDER_WORKSPACES.equals(name1) && !name2.isEmpty()) {
7✔
173
          workspace = name2;
3✔
174
        }
175
        break;
176
      }
177
      name2 = name1;
2✔
178
      int nameCount = currentDir.getNameCount();
3✔
179
      if (nameCount >= 1) {
3✔
180
        name1 = currentDir.getName(nameCount - 1).toString();
7✔
181
      }
182
      currentDir = currentDir.getParent();
3✔
183
      if ((ideRootPath != null) && (ideRootPath.equals(currentDir))) {
2!
184
        // prevent that during tests we traverse to the real IDE project of IDEasy developer
185
        currentDir = null;
×
186
      }
187
    }
1✔
188

189
    // detection completed, initializing variables
190
    this.ideRoot = findIdeRoot(currentDir);
5✔
191

192
    setCwd(workingDirectory, workspace, currentDir);
5✔
193

194
    if (this.ideRoot != null) {
3✔
195
      Path tempDownloadPath = getTempDownloadPath();
3✔
196
      if (Files.isDirectory(tempDownloadPath)) {
6✔
197
        // TODO delete all files older than 1 day here...
198
      } else {
199
        this.fileAccess.mkdirs(tempDownloadPath);
4✔
200
      }
201
    }
202

203
    this.defaultToolRepository = new DefaultToolRepository(this);
6✔
204
    loggingContext = this;
2✔
205
    this.mavenRepository = new MavenRepository(this);
6✔
206
  }
1✔
207

208
  private Path findIdeRoot(Path ideHomePath) {
209

210
    Path ideRootPath = null;
2✔
211
    if (ideHomePath != null) {
2✔
212
      ideRootPath = ideHomePath.getParent();
4✔
213
    } else if (!isTest()) {
3!
214
      ideRootPath = getIdeRootPathFromEnv();
×
215
    }
216
    return ideRootPath;
2✔
217
  }
218

219
  /**
220
   * @return the {@link #getIdeRoot() IDE_ROOT} from the system environment.
221
   */
222
  protected Path getIdeRootPathFromEnv() {
223

224
    String root = getSystem().getEnv(IdeVariables.IDE_ROOT.getName());
×
225
    if (root != null) {
×
226
      Path rootPath = Path.of(root);
×
227
      if (Files.isDirectory(rootPath)) {
×
228
        return rootPath;
×
229
      }
230
    }
231
    return null;
×
232
  }
233

234
  @Override
235
  public void setCwd(Path userDir, String workspace, Path ideHome) {
236

237
    this.cwd = userDir;
3✔
238
    this.workspaceName = workspace;
3✔
239
    this.ideHome = ideHome;
3✔
240
    if (ideHome == null) {
2✔
241
      this.workspacePath = null;
3✔
242
      this.confPath = null;
3✔
243
      this.settingsPath = null;
3✔
244
      this.pluginsPath = null;
4✔
245
    } else {
246
      this.workspacePath = this.ideHome.resolve(FOLDER_WORKSPACES).resolve(this.workspaceName);
9✔
247
      this.confPath = this.ideHome.resolve(FOLDER_CONF);
6✔
248
      this.settingsPath = this.ideHome.resolve(FOLDER_SETTINGS);
6✔
249
      this.settingsCommitIdPath = this.ideHome.resolve(IdeContext.SETTINGS_COMMIT_ID);
6✔
250
      this.pluginsPath = this.ideHome.resolve(FOLDER_PLUGINS);
6✔
251
    }
252
    if (isTest()) {
3!
253
      // only for testing...
254
      if (this.ideHome == null) {
3✔
255
        this.userHome = Path.of("/non-existing-user-home-for-testing");
7✔
256
      } else {
257
        this.userHome = this.ideHome.resolve("home");
7✔
258
      }
259
    } else {
260
      this.userHome = Path.of(getSystem().getProperty("user.home"));
×
261
    }
262
    this.userHomeIde = this.userHome.resolve(FOLDER_DOT_IDE);
6✔
263
    this.downloadPath = this.userHome.resolve("Downloads/ide");
6✔
264

265
    this.path = computeSystemPath();
4✔
266
  }
1✔
267

268
  private String getMessageIdeHomeFound() {
269

270
    return "IDE environment variables have been set for " + this.ideHome + " in workspace " + this.workspaceName;
7✔
271
  }
272

273
  private String getMessageNotInsideIdeProject() {
274

275
    return "You are not inside an IDE project: " + this.cwd;
5✔
276
  }
277

278
  private String getMessageIdeRootNotFound() {
279

280
    String root = getSystem().getEnv("IDE_ROOT");
5✔
281
    if (root == null) {
2!
282
      return "The environment variable IDE_ROOT is undefined. Please reinstall IDEasy or manually repair IDE_ROOT variable.";
2✔
283
    } else {
284
      return "The environment variable IDE_ROOT is pointing to an invalid path " + root + ". Please reinstall IDEasy or manually repair IDE_ROOT variable.";
×
285
    }
286
  }
287

288
  /**
289
   * @return {@code true} if this is a test context for JUnits, {@code false} otherwise.
290
   */
291
  public boolean isTest() {
292

293
    return false;
×
294
  }
295

296
  protected SystemPath computeSystemPath() {
297

298
    return new SystemPath(this);
×
299
  }
300

301
  private boolean isIdeHome(Path dir) {
302

303
    if (!Files.isDirectory(dir.resolve("workspaces"))) {
7✔
304
      return false;
2✔
305
    } else if (!Files.isDirectory(dir.resolve("settings"))) {
7!
306
      return false;
×
307
    }
308
    return true;
2✔
309
  }
310

311
  private EnvironmentVariables createVariables() {
312

313
    AbstractEnvironmentVariables system = createSystemVariables();
3✔
314
    AbstractEnvironmentVariables user = system.extend(this.userHomeIde, EnvironmentVariablesType.USER);
6✔
315
    AbstractEnvironmentVariables settings = user.extend(this.settingsPath, EnvironmentVariablesType.SETTINGS);
6✔
316
    AbstractEnvironmentVariables workspace = settings.extend(this.workspacePath, EnvironmentVariablesType.WORKSPACE);
6✔
317
    AbstractEnvironmentVariables conf = workspace.extend(this.confPath, EnvironmentVariablesType.CONF);
6✔
318
    return conf.resolved();
3✔
319
  }
320

321
  protected AbstractEnvironmentVariables createSystemVariables() {
322

323
    return EnvironmentVariables.ofSystem(this);
3✔
324
  }
325

326
  @Override
327
  public SystemInfo getSystemInfo() {
328

329
    return this.systemInfo;
3✔
330
  }
331

332
  @Override
333
  public FileAccess getFileAccess() {
334

335
    // currently FileAccess contains download method and requires network proxy to be configured. Maybe download should be moved to its own interface/class
336
    configureNetworkProxy();
2✔
337
    return this.fileAccess;
3✔
338
  }
339

340
  @Override
341
  public CommandletManager getCommandletManager() {
342

343
    return this.commandletManager;
3✔
344
  }
345

346
  @Override
347
  public ToolRepository getDefaultToolRepository() {
348

349
    return this.defaultToolRepository;
3✔
350
  }
351

352
  @Override
353
  public MavenRepository getMavenToolRepository() {
354

355
    return this.mavenRepository;
3✔
356
  }
357

358
  @Override
359
  public CustomToolRepository getCustomToolRepository() {
360

361
    if (this.customToolRepository == null) {
3!
362
      this.customToolRepository = CustomToolRepositoryImpl.of(this);
4✔
363
    }
364
    return this.customToolRepository;
3✔
365
  }
366

367
  @Override
368
  public Path getIdeHome() {
369

370
    return this.ideHome;
3✔
371
  }
372

373
  @Override
374
  public String getProjectName() {
375

376
    if (this.ideHome != null) {
3!
377
      return this.ideHome.getFileName().toString();
5✔
378
    }
379
    return "";
×
380
  }
381

382
  @Override
383
  public VersionIdentifier getProjectVersion() {
384

385
    if (this.ideHome != null) {
3!
386
      Path versionFile = this.ideHome.resolve(IdeContext.FILE_SOFTWARE_VERSION);
5✔
387
      if (Files.exists(versionFile)) {
5✔
388
        String version = this.fileAccess.readFileContent(versionFile).trim();
6✔
389
        return VersionIdentifier.of(version);
3✔
390
      }
391
    }
392
    return IdeMigrator.START_VERSION;
2✔
393
  }
394

395
  @Override
396
  public void setProjectVersion(VersionIdentifier version) {
397

398
    if (this.ideHome == null) {
3!
399
      throw new IllegalStateException("IDE_HOME not available!");
×
400
    }
401
    Objects.requireNonNull(version);
3✔
402
    Path versionFile = this.ideHome.resolve(IdeContext.FILE_SOFTWARE_VERSION);
5✔
403
    this.fileAccess.writeFileContent(version.toString(), versionFile);
6✔
404
  }
1✔
405

406
  @Override
407
  public Path getIdeRoot() {
408

409
    return this.ideRoot;
3✔
410
  }
411

412
  @Override
413
  public Path getIdePath() {
414

415
    Path myIdeRoot = getIdeRoot();
3✔
416
    if (myIdeRoot == null) {
2!
417
      return null;
×
418
    }
419
    return myIdeRoot.resolve(FOLDER_UNDERSCORE_IDE);
4✔
420
  }
421

422
  @Override
423
  public Path getCwd() {
424

425
    return this.cwd;
3✔
426
  }
427

428
  @Override
429
  public Path getTempPath() {
430

431
    Path idePath = getIdePath();
3✔
432
    if (idePath == null) {
2!
433
      return null;
×
434
    }
435
    return idePath.resolve("tmp");
4✔
436
  }
437

438
  @Override
439
  public Path getTempDownloadPath() {
440

441
    Path tmp = getTempPath();
3✔
442
    if (tmp == null) {
2!
443
      return null;
×
444
    }
445
    return tmp.resolve(FOLDER_DOWNLOADS);
4✔
446
  }
447

448
  @Override
449
  public Path getUserHome() {
450

451
    return this.userHome;
3✔
452
  }
453

454
  @Override
455
  public Path getUserHomeIde() {
456

457
    return this.userHomeIde;
3✔
458
  }
459

460
  @Override
461
  public Path getSettingsPath() {
462

463
    return this.settingsPath;
3✔
464
  }
465

466
  @Override
467
  public Path getSettingsGitRepository() {
468

469
    Path settingsPath = getSettingsPath();
3✔
470
    // check whether the settings path has a .git folder only if its not a symbolic link or junction
471
    if ((settingsPath != null) && !Files.exists(settingsPath.resolve(".git")) && !isSettingsRepositorySymlinkOrJunction()) {
12!
472
      error("Settings repository exists but is not a git repository.");
3✔
473
      return null;
2✔
474
    }
475
    return settingsPath;
2✔
476
  }
477

478
  @Override
479
  public boolean isSettingsRepositorySymlinkOrJunction() {
480

481
    Path settingsPath = getSettingsPath();
3✔
482
    if (settingsPath == null) {
2!
483
      return false;
×
484
    }
485
    return Files.isSymbolicLink(settingsPath) || getFileAccess().isJunction(settingsPath);
10!
486
  }
487

488
  @Override
489
  public Path getSettingsCommitIdPath() {
490

491
    return this.settingsCommitIdPath;
3✔
492
  }
493

494
  @Override
495
  public Path getConfPath() {
496

497
    return this.confPath;
3✔
498
  }
499

500
  @Override
501
  public Path getSoftwarePath() {
502

503
    if (this.ideHome == null) {
3✔
504
      return null;
2✔
505
    }
506
    return this.ideHome.resolve(FOLDER_SOFTWARE);
5✔
507
  }
508

509
  @Override
510
  public Path getSoftwareExtraPath() {
511

512
    Path softwarePath = getSoftwarePath();
3✔
513
    if (softwarePath == null) {
2!
514
      return null;
×
515
    }
516
    return softwarePath.resolve(FOLDER_EXTRA);
4✔
517
  }
518

519
  @Override
520
  public Path getSoftwareRepositoryPath() {
521

522
    Path idePath = getIdePath();
3✔
523
    if (idePath == null) {
2!
524
      return null;
×
525
    }
526
    return idePath.resolve(FOLDER_SOFTWARE);
4✔
527
  }
528

529
  @Override
530
  public Path getPluginsPath() {
531

532
    return this.pluginsPath;
3✔
533
  }
534

535
  @Override
536
  public String getWorkspaceName() {
537

538
    return this.workspaceName;
3✔
539
  }
540

541
  @Override
542
  public Path getWorkspacePath() {
543

544
    return this.workspacePath;
3✔
545
  }
546

547
  @Override
548
  public Path getDownloadPath() {
549

550
    return this.downloadPath;
3✔
551
  }
552

553
  @Override
554
  public Path getUrlsPath() {
555

556
    Path idePath = getIdePath();
3✔
557
    if (idePath == null) {
2!
558
      return null;
×
559
    }
560
    return idePath.resolve(FOLDER_URLS);
4✔
561
  }
562

563
  @Override
564
  public Path getToolRepositoryPath() {
565

566
    Path idePath = getIdePath();
3✔
567
    if (idePath == null) {
2!
568
      return null;
×
569
    }
570
    return idePath.resolve(FOLDER_SOFTWARE);
4✔
571
  }
572

573
  @Override
574
  public SystemPath getPath() {
575

576
    return this.path;
3✔
577
  }
578

579
  @Override
580
  public EnvironmentVariables getVariables() {
581

582
    if (this.variables == null) {
3✔
583
      this.variables = createVariables();
4✔
584
    }
585
    return this.variables;
3✔
586
  }
587

588
  @Override
589
  public UrlMetadata getUrls() {
590

591
    if (this.urlMetadata == null) {
3✔
592
      if (!isTest()) {
3!
593
        getGitContext().pullOrCloneAndResetIfNeeded(IDE_URLS_GIT, getUrlsPath(), null);
×
594
      }
595
      this.urlMetadata = new UrlMetadata(this);
6✔
596
    }
597
    return this.urlMetadata;
3✔
598
  }
599

600
  @Override
601
  public boolean isQuietMode() {
602

603
    return this.startContext.isQuietMode();
4✔
604
  }
605

606
  @Override
607
  public boolean isBatchMode() {
608

609
    return this.startContext.isBatchMode();
×
610
  }
611

612
  @Override
613
  public boolean isForceMode() {
614

615
    return this.startContext.isForceMode();
4✔
616
  }
617

618
  @Override
619
  public boolean isForcePull() {
620

621
    return this.startContext.isForcePull();
×
622
  }
623

624
  @Override
625
  public boolean isForcePlugins() {
626

627
    return this.startContext.isForcePlugins();
×
628
  }
629

630
  @Override
631
  public boolean isForceRepositories() {
632

633
    return this.startContext.isForceRepositories();
×
634
  }
635

636
  @Override
637
  public boolean isOfflineMode() {
638

639
    return this.startContext.isOfflineMode();
4✔
640
  }
641

642
  @Override
643
  public boolean isSkipUpdatesMode() {
644

645
    return this.startContext.isSkipUpdatesMode();
4✔
646
  }
647

648
  @Override
649
  public boolean isOnline() {
650

651
    if (this.online == null) {
3✔
652
      configureNetworkProxy();
2✔
653
      // we currently assume we have only a CLI process that runs shortly
654
      // therefore we run this check only once to save resources when this method is called many times
655
      try {
656
        int timeout = 1000;
2✔
657
        //open a connection to github.com and try to retrieve data
658
        //getContent fails if there is no connection
659
        URLConnection connection = new URL("https://www.github.com").openConnection();
6✔
660
        connection.setConnectTimeout(timeout);
3✔
661
        connection.getContent();
3✔
662
        this.online = Boolean.TRUE;
3✔
663
      } catch (Exception ignored) {
×
664
        this.online = Boolean.FALSE;
×
665
      }
1✔
666
    }
667
    return this.online.booleanValue();
4✔
668
  }
669

670
  private void configureNetworkProxy() {
671

672
    if (this.networkProxy == null) {
3✔
673
      this.networkProxy = new NetworkProxy(this);
6✔
674
      this.networkProxy.configure();
3✔
675
    }
676
  }
1✔
677

678
  @Override
679
  public Locale getLocale() {
680

681
    Locale locale = this.startContext.getLocale();
4✔
682
    if (locale == null) {
2✔
683
      locale = Locale.getDefault();
2✔
684
    }
685
    return locale;
2✔
686
  }
687

688
  @Override
689
  public DirectoryMerger getWorkspaceMerger() {
690

691
    if (this.workspaceMerger == null) {
3✔
692
      this.workspaceMerger = new DirectoryMerger(this);
6✔
693
    }
694
    return this.workspaceMerger;
3✔
695
  }
696

697
  /**
698
   * @return the {@link #getDefaultExecutionDirectory() default execution directory} in which a command process is executed.
699
   */
700
  @Override
701
  public Path getDefaultExecutionDirectory() {
702

703
    return this.defaultExecutionDirectory;
×
704
  }
705

706
  /**
707
   * @param defaultExecutionDirectory new value of {@link #getDefaultExecutionDirectory()}.
708
   */
709
  public void setDefaultExecutionDirectory(Path defaultExecutionDirectory) {
710

711
    if (defaultExecutionDirectory != null) {
×
712
      this.defaultExecutionDirectory = defaultExecutionDirectory;
×
713
    }
714
  }
×
715

716
  @Override
717
  public GitContext getGitContext() {
718

719
    return new GitContextImpl(this);
×
720
  }
721

722
  @Override
723
  public ProcessContext newProcess() {
724

725
    ProcessContext processContext = createProcessContext();
3✔
726
    if (this.defaultExecutionDirectory != null) {
3!
727
      processContext.directory(this.defaultExecutionDirectory);
×
728
    }
729
    return processContext;
2✔
730
  }
731

732
  @Override
733
  public IdeSystem getSystem() {
734

735
    if (this.system == null) {
×
736
      this.system = new IdeSystemImpl(this);
×
737
    }
738
    return this.system;
×
739
  }
740

741
  /**
742
   * @return a new instance of {@link ProcessContext}.
743
   * @see #newProcess()
744
   */
745
  protected ProcessContext createProcessContext() {
746

747
    return new ProcessContextImpl(this);
5✔
748
  }
749

750
  @Override
751
  public IdeSubLogger level(IdeLogLevel level) {
752

753
    return this.startContext.level(level);
5✔
754
  }
755

756
  @Override
757
  public void logIdeHomeAndRootStatus() {
758

759
    if (this.ideRoot != null) {
3!
760
      success("IDE_ROOT is set to {}", this.ideRoot);
×
761
    }
762
    if (this.ideHome == null) {
3!
763
      warning(getMessageNotInsideIdeProject());
5✔
764
    } else {
765
      success("IDE_HOME is set to {}", this.ideHome);
×
766
    }
767
  }
1✔
768

769
  @Override
770
  public String askForInput(String message, String defaultValue) {
771

772
    if (!message.isBlank()) {
×
773
      info(message);
×
774
    }
775
    if (isBatchMode()) {
×
776
      if (isForceMode() || isForcePull()) {
×
777
        return defaultValue;
×
778
      } else {
779
        throw new CliAbortException();
×
780
      }
781
    }
782
    String input = readLine().trim();
×
783
    return input.isEmpty() ? defaultValue : input;
×
784
  }
785

786
  @Override
787
  public String askForInput(String message) {
788

789
    String input;
790
    do {
791
      info(message);
3✔
792
      input = readLine().trim();
4✔
793
    } while (input.isEmpty());
3!
794

795
    return input;
2✔
796
  }
797

798
  @SuppressWarnings("unchecked")
799
  @Override
800
  public <O> O question(String question, O... options) {
801

802
    assert (options.length >= 2);
×
803
    interaction(question);
×
804
    Map<String, O> mapping = new HashMap<>(options.length);
×
805
    int i = 0;
×
806
    for (O option : options) {
×
807
      i++;
×
808
      String key = "" + option;
×
809
      addMapping(mapping, key, option);
×
810
      String numericKey = Integer.toString(i);
×
811
      if (numericKey.equals(key)) {
×
812
        trace("Options should not be numeric: " + key);
×
813
      } else {
814
        addMapping(mapping, numericKey, option);
×
815
      }
816
      interaction("Option " + numericKey + ": " + key);
×
817
    }
818
    O option = null;
×
819
    if (isBatchMode()) {
×
820
      if (isForceMode() || isForcePull()) {
×
821
        option = options[0];
×
822
        interaction("" + option);
×
823
      }
824
    } else {
825
      while (option == null) {
×
826
        String answer = readLine();
×
827
        option = mapping.get(answer);
×
828
        if (option == null) {
×
829
          warning("Invalid answer: '" + answer + "' - please try again.");
×
830
        }
831
      }
×
832
    }
833
    return option;
×
834
  }
835

836
  /**
837
   * @return the input from the end-user (e.g. read from the console).
838
   */
839
  protected abstract String readLine();
840

841
  private static <O> void addMapping(Map<String, O> mapping, String key, O option) {
842

843
    O duplicate = mapping.put(key, option);
×
844
    if (duplicate != null) {
×
845
      throw new IllegalArgumentException("Duplicated option " + key);
×
846
    }
847
  }
×
848

849
  @Override
850
  public Step getCurrentStep() {
851

852
    return this.currentStep;
×
853
  }
854

855
  @Override
856
  public StepImpl newStep(boolean silent, String name, Object... parameters) {
857

858
    this.currentStep = new StepImpl(this, this.currentStep, name, silent, parameters);
11✔
859
    return this.currentStep;
3✔
860
  }
861

862
  /**
863
   * Internal method to end the running {@link Step}.
864
   *
865
   * @param step the current {@link Step} to end.
866
   */
867
  public void endStep(StepImpl step) {
868

869
    if (step == this.currentStep) {
4!
870
      this.currentStep = this.currentStep.getParent();
6✔
871
    } else {
872
      String currentStepName = "null";
×
873
      if (this.currentStep != null) {
×
874
        currentStepName = this.currentStep.getName();
×
875
      }
876
      warning("endStep called with wrong step '{}' but expected '{}'", step.getName(), currentStepName);
×
877
    }
878
  }
1✔
879

880
  /**
881
   * Finds the matching {@link Commandlet} to run, applies {@link CliArguments} to its {@link Commandlet#getProperties() properties} and will execute it.
882
   *
883
   * @param arguments the {@link CliArgument}.
884
   * @return the return code of the execution.
885
   */
886
  public int run(CliArguments arguments) {
887

888
    CliArgument current = arguments.current();
3✔
889
    assert (this.currentStep == null);
4!
890
    boolean supressStepSuccess = false;
2✔
891
    StepImpl step = newStep(true, "ide", (Object[]) current.asArray());
8✔
892
    Iterator<Commandlet> commandletIterator = this.commandletManager.findCommandlet(arguments, null);
6✔
893
    Commandlet cmd = null;
2✔
894
    ValidationResult result = null;
2✔
895
    try {
896
      while (commandletIterator.hasNext()) {
3!
897
        cmd = commandletIterator.next();
4✔
898
        result = applyAndRun(arguments.copy(), cmd);
6✔
899
        if (result.isValid()) {
3!
900
          supressStepSuccess = cmd.isSuppressStepSuccess();
3✔
901
          step.success();
2✔
902
          return ProcessResult.SUCCESS;
4✔
903
        }
904
      }
905
      this.startContext.activateLogging();
×
906
      if (result != null) {
×
907
        error(result.getErrorMessage());
×
908
      }
909
      step.error("Invalid arguments: {}", current.getArgs());
×
910
      HelpCommandlet help = this.commandletManager.getCommandlet(HelpCommandlet.class);
×
911
      if (cmd != null) {
×
912
        help.commandlet.setValue(cmd);
×
913
      }
914
      help.run();
×
915
      return 1;
×
916
    } catch (Throwable t) {
1✔
917
      this.startContext.activateLogging();
3✔
918
      step.error(t, true);
4✔
919
      throw t;
2✔
920
    } finally {
921
      step.close();
2✔
922
      assert (this.currentStep == null);
4!
923
      step.logSummary(supressStepSuccess);
3✔
924
    }
925
  }
926

927
  /**
928
   * @param cmd the potential {@link Commandlet} to {@link #apply(CliArguments, Commandlet) apply} and {@link Commandlet#run() run}.
929
   * @return {@code true} if the given {@link Commandlet} matched and did {@link Commandlet#run() run} successfully, {@code false} otherwise (the
930
   *     {@link Commandlet} did not match and we have to try a different candidate).
931
   */
932
  private ValidationResult applyAndRun(CliArguments arguments, Commandlet cmd) {
933

934
    IdeLogLevel previousLogLevel = null;
2✔
935
    cmd.reset();
2✔
936
    ValidationResult result = apply(arguments, cmd);
5✔
937
    if (result.isValid()) {
3!
938
      result = cmd.validate();
3✔
939
    }
940
    if (result.isValid()) {
3!
941
      debug("Running commandlet {}", cmd);
9✔
942
      if (cmd.isIdeHomeRequired() && (this.ideHome == null)) {
6!
943
        throw new CliException(getMessageNotInsideIdeProject(), ProcessResult.NO_IDE_HOME);
×
944
      } else if (cmd.isIdeRootRequired() && (this.ideRoot == null)) {
6!
945
        throw new CliException(getMessageIdeRootNotFound(), ProcessResult.NO_IDE_ROOT);
7✔
946
      }
947
      try {
948
        if (cmd.isProcessableOutput()) {
3!
949
          if (!debug().isEnabled()) {
×
950
            // unless --debug or --trace was supplied, processable output commandlets will disable all log-levels except INFO to prevent other logs interfere
951
            previousLogLevel = this.startContext.setLogLevel(IdeLogLevel.PROCESSABLE);
×
952
          }
953
          this.startContext.activateLogging();
×
954
        } else {
955
          this.startContext.activateLogging();
3✔
956
          verifyIdeRoot();
2✔
957
          if (cmd.isIdeHomeRequired()) {
3!
958
            debug(getMessageIdeHomeFound());
4✔
959
          }
960
          Path settingsRepository = getSettingsGitRepository();
3✔
961
          if (settingsRepository != null) {
2!
962
            if (getGitContext().isRepositoryUpdateAvailable(settingsRepository, getSettingsCommitIdPath()) || (
×
963
                getGitContext().fetchIfNeeded(settingsRepository) && getGitContext().isRepositoryUpdateAvailable(
×
964
                    settingsRepository, getSettingsCommitIdPath()))) {
×
965
              if (isSettingsRepositorySymlinkOrJunction()) {
×
966
                interaction(
×
967
                    "Updates are available for the settings repository. Please pull the latest changes by yourself or by calling \"ide -f update\" to apply them.");
968

969
              } else {
970
                interaction(
×
971
                    "Updates are available for the settings repository. If you want to apply the latest changes, call \"ide update\"");
972
              }
973
            }
974
          }
975
        }
976
        boolean success = ensureLicenseAgreement(cmd);
4✔
977
        if (!success) {
2!
978
          return ValidationResultValid.get();
×
979
        }
980
        cmd.run();
2✔
981
      } finally {
982
        if (previousLogLevel != null) {
2!
983
          this.startContext.setLogLevel(previousLogLevel);
×
984
        }
985
      }
1✔
986
    } else {
987
      trace("Commandlet did not match");
×
988
    }
989
    return result;
2✔
990
  }
991

992
  private boolean ensureLicenseAgreement(Commandlet cmd) {
993

994
    if (isTest()) {
3!
995
      return true; // ignore for tests
2✔
996
    }
997
    getFileAccess().mkdirs(this.userHomeIde);
×
998
    Path licenseAgreement = this.userHomeIde.resolve(FILE_LICENSE_AGREEMENT);
×
999
    if (Files.isRegularFile(licenseAgreement)) {
×
1000
      return true; // success, license already accepted
×
1001
    }
1002
    if (cmd instanceof EnvironmentCommandlet) {
×
1003
      // if the license was not accepted, "$(ideasy env --bash)" that is written into a variable prevents the user from seeing the question he is asked
1004
      // in such situation the user could not open a bash terminal anymore and gets blocked what would really annoy the user so we exit here without doing or
1005
      // printing anything anymore in such case.
1006
      return false;
×
1007
    }
1008
    boolean logLevelInfoDisabled = !this.startContext.info().isEnabled();
×
1009
    if (logLevelInfoDisabled) {
×
1010
      this.startContext.setLogLevel(IdeLogLevel.INFO, true);
×
1011
    }
1012
    boolean logLevelInteractionDisabled = !this.startContext.interaction().isEnabled();
×
1013
    if (logLevelInteractionDisabled) {
×
1014
      this.startContext.setLogLevel(IdeLogLevel.INTERACTION, true);
×
1015
    }
1016
    StringBuilder sb = new StringBuilder(1180);
×
1017
    sb.append(LOGO).append("""
×
1018
        Welcome to IDEasy!
1019
        This product (with its included 3rd party components) is open-source software and can be used free (also commercially).
1020
        It supports automatic download and installation of arbitrary 3rd party tools.
1021
        By default only open-source 3rd party tools are used (downloaded, installed, executed).
1022
        But if explicitly configured, also commercial software that requires an additional license may be used.
1023
        This happens e.g. if you configure "ultimate" edition of IntelliJ or "docker" edition of Docker (Docker Desktop).
1024
        You are solely responsible for all risks implied by using this software.
1025
        Before using IDEasy you need to read and accept the license agreement with all involved licenses.
1026
        You will be able to find it online under the following URL:
1027
        """).append(LICENSE_URL);
×
1028
    if (this.ideRoot != null) {
×
1029
      sb.append("\n\nAlso it is included in the documentation that you can find here:\n").
×
1030
          append(getIdePath().resolve("IDEasy.pdf").toString()).append("\n");
×
1031
    }
1032
    info(sb.toString());
×
1033
    askToContinue("Do you accept these terms of use and all license agreements?");
×
1034

1035
    sb.setLength(0);
×
1036
    LocalDateTime now = LocalDateTime.now();
×
1037
    sb.append("On ").append(DateTimeUtil.formatDate(now, false)).append(" at ").append(DateTimeUtil.formatTime(now))
×
1038
        .append(" you accepted the IDEasy license.\n").append(LICENSE_URL);
×
1039
    try {
1040
      Files.writeString(licenseAgreement, sb);
×
1041
    } catch (Exception e) {
×
1042
      throw new RuntimeException("Failed to save license agreement!", e);
×
1043
    }
×
1044
    if (logLevelInfoDisabled) {
×
1045
      this.startContext.setLogLevel(IdeLogLevel.INFO, false);
×
1046
    }
1047
    if (logLevelInteractionDisabled) {
×
1048
      this.startContext.setLogLevel(IdeLogLevel.INTERACTION, false);
×
1049
    }
1050
    return true;
×
1051
  }
1052

1053
  private void verifyIdeRoot() {
1054

1055
    if (!isTest()) {
3!
1056
      if (this.ideRoot == null) {
×
1057
        warning("Variable IDE_ROOT is undefined. Please check your installation or run setup script again.");
×
1058
      } else if (this.ideHome != null) {
×
1059
        Path ideRootPath = getIdeRootPathFromEnv();
×
1060
        if (!this.ideRoot.equals(ideRootPath)) {
×
1061
          warning("Variable IDE_ROOT is set to '{}' but for your project '{}' the path '{}' would have been expected.", ideRootPath,
×
1062
              this.ideHome.getFileName(), this.ideRoot);
×
1063
        }
1064
      }
1065
    }
1066
  }
1✔
1067

1068
  /**
1069
   * @param arguments the {@link CliArguments#ofCompletion(String...) completion arguments}.
1070
   * @param includeContextOptions to include the options of {@link ContextCommandlet}.
1071
   * @return the {@link List} of {@link CompletionCandidate}s to suggest.
1072
   */
1073
  public List<CompletionCandidate> complete(CliArguments arguments, boolean includeContextOptions) {
1074

1075
    CompletionCandidateCollector collector = new CompletionCandidateCollectorDefault(this);
5✔
1076
    if (arguments.current().isStart()) {
4✔
1077
      arguments.next();
3✔
1078
    }
1079
    if (includeContextOptions) {
2✔
1080
      ContextCommandlet cc = new ContextCommandlet();
4✔
1081
      for (Property<?> property : cc.getProperties()) {
11✔
1082
        assert (property.isOption());
4!
1083
        property.apply(arguments, this, cc, collector);
7✔
1084
      }
1✔
1085
    }
1086
    Iterator<Commandlet> commandletIterator = this.commandletManager.findCommandlet(arguments, collector);
6✔
1087
    CliArgument current = arguments.current();
3✔
1088
    if (current.isCompletion() && current.isCombinedShortOption()) {
6✔
1089
      collector.add(current.get(), null, null, null);
7✔
1090
    }
1091
    arguments.next();
3✔
1092
    while (commandletIterator.hasNext()) {
3✔
1093
      Commandlet cmd = commandletIterator.next();
4✔
1094
      if (!arguments.current().isEnd()) {
4✔
1095
        completeCommandlet(arguments.copy(), cmd, collector);
6✔
1096
      }
1097
    }
1✔
1098
    return collector.getSortedCandidates();
3✔
1099
  }
1100

1101
  private void completeCommandlet(CliArguments arguments, Commandlet cmd, CompletionCandidateCollector collector) {
1102

1103
    trace("Trying to match arguments for auto-completion for commandlet {}", cmd.getName());
10✔
1104
    Iterator<Property<?>> valueIterator = cmd.getValues().iterator();
4✔
1105
    valueIterator.next(); // skip first property since this is the keyword property that already matched to find the commandlet
3✔
1106
    List<Property<?>> properties = cmd.getProperties();
3✔
1107
    // we are creating our own list of options and remove them when matched to avoid duplicate suggestions
1108
    List<Property<?>> optionProperties = new ArrayList<>(properties.size());
6✔
1109
    for (Property<?> property : properties) {
10✔
1110
      if (property.isOption()) {
3✔
1111
        optionProperties.add(property);
4✔
1112
      }
1113
    }
1✔
1114
    CliArgument currentArgument = arguments.current();
3✔
1115
    while (!currentArgument.isEnd()) {
3✔
1116
      trace("Trying to match argument '{}'", currentArgument);
9✔
1117
      if (currentArgument.isOption() && !arguments.isEndOptions()) {
6!
1118
        if (currentArgument.isCompletion()) {
3✔
1119
          Iterator<Property<?>> optionIterator = optionProperties.iterator();
3✔
1120
          while (optionIterator.hasNext()) {
3✔
1121
            Property<?> option = optionIterator.next();
4✔
1122
            boolean success = option.apply(arguments, this, cmd, collector);
7✔
1123
            if (success) {
2✔
1124
              optionIterator.remove();
2✔
1125
              arguments.next();
3✔
1126
            }
1127
          }
1✔
1128
        } else {
1✔
1129
          Property<?> option = cmd.getOption(currentArgument.get());
5✔
1130
          if (option != null) {
2✔
1131
            arguments.next();
3✔
1132
            boolean removed = optionProperties.remove(option);
4✔
1133
            if (!removed) {
2!
1134
              option = null;
×
1135
            }
1136
          }
1137
          if (option == null) {
2✔
1138
            trace("No such option was found.");
3✔
1139
            return;
1✔
1140
          }
1141
        }
1✔
1142
      } else {
1143
        if (valueIterator.hasNext()) {
3✔
1144
          Property<?> valueProperty = valueIterator.next();
4✔
1145
          boolean success = valueProperty.apply(arguments, this, cmd, collector);
7✔
1146
          if (!success) {
2✔
1147
            trace("Completion cannot match any further.");
3✔
1148
            return;
1✔
1149
          }
1150
        } else {
1✔
1151
          trace("No value left for completion.");
3✔
1152
          return;
1✔
1153
        }
1154
      }
1155
      currentArgument = arguments.current();
4✔
1156
    }
1157
  }
1✔
1158

1159
  /**
1160
   * @param arguments the {@link CliArguments} to apply. Will be {@link CliArguments#next() consumed} as they are matched. Consider passing a
1161
   *     {@link CliArguments#copy() copy} as needed.
1162
   * @param cmd the potential {@link Commandlet} to match.
1163
   * @return the {@link ValidationResult} telling if the {@link CliArguments} can be applied successfully or if validation errors ocurred.
1164
   */
1165
  public ValidationResult apply(CliArguments arguments, Commandlet cmd) {
1166

1167
    trace("Trying to match arguments to commandlet {}", cmd.getName());
10✔
1168
    CliArgument currentArgument = arguments.current();
3✔
1169
    Iterator<Property<?>> propertyIterator = cmd.getValues().iterator();
4✔
1170
    Property<?> property = null;
2✔
1171
    if (propertyIterator.hasNext()) {
3!
1172
      property = propertyIterator.next();
4✔
1173
    }
1174
    while (!currentArgument.isEnd()) {
3✔
1175
      trace("Trying to match argument '{}'", currentArgument);
9✔
1176
      Property<?> currentProperty = property;
2✔
1177
      if (!arguments.isEndOptions()) {
3!
1178
        Property<?> option = cmd.getOption(currentArgument.getKey());
5✔
1179
        if (option != null) {
2!
1180
          currentProperty = option;
×
1181
        }
1182
      }
1183
      if (currentProperty == null) {
2!
1184
        trace("No option or next value found");
×
1185
        ValidationState state = new ValidationState(null);
×
1186
        state.addErrorMessage("No matching property found");
×
1187
        return state;
×
1188
      }
1189
      trace("Next property candidate to match argument is {}", currentProperty);
9✔
1190
      if (currentProperty == property) {
3!
1191
        if (!property.isMultiValued()) {
3✔
1192
          if (propertyIterator.hasNext()) {
3✔
1193
            property = propertyIterator.next();
5✔
1194
          } else {
1195
            property = null;
2✔
1196
          }
1197
        }
1198
        if ((property != null) && property.isValue() && property.isMultiValued()) {
8!
1199
          arguments.stopSplitShortOptions();
2✔
1200
        }
1201
      }
1202
      boolean matches = currentProperty.apply(arguments, this, cmd, null);
7✔
1203
      if (!matches) {
2!
1204
        ValidationState state = new ValidationState(null);
×
1205
        state.addErrorMessage("No matching property found");
×
1206
        return state;
×
1207
      }
1208
      currentArgument = arguments.current();
3✔
1209
    }
1✔
1210
    return ValidationResultValid.get();
2✔
1211
  }
1212

1213
  @Override
1214
  public String findBash() {
1215

1216
    String bash = "bash";
2✔
1217
    if (SystemInfoImpl.INSTANCE.isWindows()) {
3!
1218
      bash = findBashOnWindows();
×
1219
    }
1220

1221
    return bash;
2✔
1222
  }
1223

1224
  private String findBashOnWindows() {
1225

1226
    // Check if Git Bash exists in the default location
1227
    Path defaultPath = Path.of("C:\\Program Files\\Git\\bin\\bash.exe");
×
1228
    if (Files.exists(defaultPath)) {
×
1229
      return defaultPath.toString();
×
1230
    }
1231

1232
    // If not found in the default location, try the registry query
1233
    String[] bashVariants = { "GitForWindows", "Cygwin\\setup" };
×
1234
    String[] registryKeys = { "HKEY_LOCAL_MACHINE", "HKEY_CURRENT_USER" };
×
1235
    String regQueryResult;
1236
    for (String bashVariant : bashVariants) {
×
1237
      for (String registryKey : registryKeys) {
×
1238
        String toolValueName = ("GitForWindows".equals(bashVariant)) ? "InstallPath" : "rootdir";
×
1239
        String command = "reg query " + registryKey + "\\Software\\" + bashVariant + "  /v " + toolValueName + " 2>nul";
×
1240

1241
        try {
1242
          Process process = new ProcessBuilder("cmd.exe", "/c", command).start();
×
1243
          try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
×
1244
            StringBuilder output = new StringBuilder();
×
1245
            String line;
1246

1247
            while ((line = reader.readLine()) != null) {
×
1248
              output.append(line);
×
1249
            }
1250

1251
            int exitCode = process.waitFor();
×
1252
            if (exitCode != 0) {
×
1253
              return null;
×
1254
            }
1255

1256
            regQueryResult = output.toString();
×
1257
            if (regQueryResult != null) {
×
1258
              int index = regQueryResult.indexOf("REG_SZ");
×
1259
              if (index != -1) {
×
1260
                String path = regQueryResult.substring(index + "REG_SZ".length()).trim();
×
1261
                return path + "\\bin\\bash.exe";
×
1262
              }
1263
            }
1264

1265
          }
×
1266
        } catch (Exception e) {
×
1267
          return null;
×
1268
        }
×
1269
      }
1270
    }
1271
    // no bash found
1272
    return null;
×
1273
  }
1274

1275
  @Override
1276
  public WindowsPathSyntax getPathSyntax() {
1277

1278
    return this.pathSyntax;
3✔
1279
  }
1280

1281
  /**
1282
   * @param pathSyntax new value of {@link #getPathSyntax()}.
1283
   */
1284
  public void setPathSyntax(WindowsPathSyntax pathSyntax) {
1285

1286
    this.pathSyntax = pathSyntax;
3✔
1287
  }
1✔
1288

1289
  /**
1290
   * @return the {@link IdeStartContextImpl}.
1291
   */
1292
  public IdeStartContextImpl getStartContext() {
1293

1294
    return startContext;
3✔
1295
  }
1296

1297
  /**
1298
   * @return the {@link WindowsHelper}.
1299
   */
1300
  public final WindowsHelper getWindowsHelper() {
1301

1302
    if (this.windowsHelper == null) {
3✔
1303
      this.windowsHelper = createWindowsHelper();
4✔
1304
    }
1305
    return this.windowsHelper;
3✔
1306
  }
1307

1308
  /**
1309
   * @return the new {@link WindowsHelper} instance.
1310
   */
1311
  protected WindowsHelper createWindowsHelper() {
1312

1313
    return new WindowsHelperImpl(this);
×
1314
  }
1315

1316
  /**
1317
   * Reloads this context and re-initializes the {@link #getVariables() variables}.
1318
   */
1319
  public void reload() {
1320

1321
    this.variables = null;
3✔
1322
    this.customToolRepository = null;
3✔
1323
  }
1✔
1324

1325
  @Override
1326
  public void writeVersionFile(VersionIdentifier version, Path installationPath) {
1327

1328
    assert (Files.isDirectory(installationPath));
6!
1329
    Path versionFile = installationPath.resolve(FILE_SOFTWARE_VERSION);
4✔
1330
    getFileAccess().writeFileContent(version.toString(), versionFile);
6✔
1331
  }
1✔
1332

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