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

devonfw / IDEasy / 15558566225

10 Jun 2025 11:42AM UTC coverage: 67.653% (-0.09%) from 67.746%
15558566225

Pull #1017

github

web-flow
Merge cbe54f975 into 7be183ce9
Pull Request #1017: #404: enhance logging with custom slf4j bridge

3182 of 5106 branches covered (62.32%)

Branch coverage included in aggregate %.

8200 of 11718 relevant lines covered (69.98%)

3.06 hits per line

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

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

3
import static com.devonfw.tools.ide.variable.IdeVariables.IDE_MIN_VERSION;
4

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

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

75
/**
76
 * Abstract base implementation of {@link IdeContext}.
77
 */
78
public abstract class AbstractIdeContext implements IdeContext {
79

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

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

84
  private final IdeStartContextImpl startContext;
85

86
  private Path ideHome;
87

88
  private final Path ideRoot;
89

90
  private Path confPath;
91

92
  protected Path settingsPath;
93

94
  private Path settingsCommitIdPath;
95

96
  protected Path pluginsPath;
97

98
  private Path workspacePath;
99

100
  private String workspaceName;
101

102
  private Path cwd;
103

104
  private Path downloadPath;
105

106
  protected Path userHome;
107

108
  private Path userHomeIde;
109

110
  private SystemPath path;
111

112
  private WindowsPathSyntax pathSyntax;
113

114
  private final SystemInfo systemInfo;
115

116
  private EnvironmentVariables variables;
117

118
  private final FileAccess fileAccess;
119

120
  protected CommandletManager commandletManager;
121

122
  protected ToolRepository defaultToolRepository;
123

124
  private CustomToolRepository customToolRepository;
125

126
  private MavenRepository mavenRepository;
127

128
  private DirectoryMerger workspaceMerger;
129

130
  protected UrlMetadata urlMetadata;
131

132
  protected Path defaultExecutionDirectory;
133

134
  private StepImpl currentStep;
135

136
  protected Boolean online;
137

138
  protected IdeSystem system;
139

140
  private NetworkProxy networkProxy;
141

142
  private WindowsHelper windowsHelper;
143

144
  /** Context used for logging */
145
  public static IdeContext loggingContext;
146

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

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

192
    // detection completed, initializing variables
193
    this.ideRoot = findIdeRoot(currentDir);
5✔
194

195
    setCwd(workingDirectory, workspace, currentDir);
5✔
196

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

206
    this.defaultToolRepository = new DefaultToolRepository(this);
6✔
207
    loggingContext = this;
2✔
208
    this.mavenRepository = new MavenRepository(this);
6✔
209
  }
1✔
210

211
  private Path findIdeRoot(Path ideHomePath) {
212

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

222
  /**
223
   * @return the {@link #getIdeRoot() IDE_ROOT} from the system environment.
224
   */
225
  protected Path getIdeRootPathFromEnv() {
226

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

237
  @Override
238
  public void setCwd(Path userDir, String workspace, Path ideHome) {
239

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

268
    this.path = computeSystemPath();
4✔
269
  }
1✔
270

271
  private String getMessageIdeHomeFound() {
272

273
    return "IDE environment variables have been set for " + this.ideHome + " in workspace " + this.workspaceName;
7✔
274
  }
275

276
  private String getMessageNotInsideIdeProject() {
277

278
    return "You are not inside an IDE project: " + this.cwd;
5✔
279
  }
280

281
  private String getMessageIdeRootNotFound() {
282

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

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

296
    return false;
×
297
  }
298

299
  protected SystemPath computeSystemPath() {
300

301
    return new SystemPath(this);
×
302
  }
303

304
  private boolean isIdeHome(Path dir) {
305

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

314
  private EnvironmentVariables createVariables() {
315

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

324
  protected AbstractEnvironmentVariables createSystemVariables() {
325

326
    return EnvironmentVariables.ofSystem(this);
3✔
327
  }
328

329
  @Override
330
  public SystemInfo getSystemInfo() {
331

332
    return this.systemInfo;
3✔
333
  }
334

335
  @Override
336
  public FileAccess getFileAccess() {
337

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

343
  @Override
344
  public CommandletManager getCommandletManager() {
345

346
    return this.commandletManager;
3✔
347
  }
348

349
  @Override
350
  public ToolRepository getDefaultToolRepository() {
351

352
    return this.defaultToolRepository;
3✔
353
  }
354

355
  @Override
356
  public MavenRepository getMavenToolRepository() {
357

358
    return this.mavenRepository;
3✔
359
  }
360

361
  @Override
362
  public CustomToolRepository getCustomToolRepository() {
363

364
    if (this.customToolRepository == null) {
3!
365
      this.customToolRepository = CustomToolRepositoryImpl.of(this);
4✔
366
    }
367
    return this.customToolRepository;
3✔
368
  }
369

370
  @Override
371
  public Path getIdeHome() {
372

373
    return this.ideHome;
3✔
374
  }
375

376
  @Override
377
  public String getProjectName() {
378

379
    if (this.ideHome != null) {
3!
380
      return this.ideHome.getFileName().toString();
5✔
381
    }
382
    return "";
×
383
  }
384

385
  @Override
386
  public VersionIdentifier getProjectVersion() {
387

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

398
  @Override
399
  public void setProjectVersion(VersionIdentifier version) {
400

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

409
  @Override
410
  public Path getIdeRoot() {
411

412
    return this.ideRoot;
3✔
413
  }
414

415
  @Override
416
  public Path getIdePath() {
417

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

425
  @Override
426
  public Path getCwd() {
427

428
    return this.cwd;
3✔
429
  }
430

431
  @Override
432
  public Path getTempPath() {
433

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

441
  @Override
442
  public Path getTempDownloadPath() {
443

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

451
  @Override
452
  public Path getUserHome() {
453

454
    return this.userHome;
3✔
455
  }
456

457
  @Override
458
  public Path getUserHomeIde() {
459

460
    return this.userHomeIde;
3✔
461
  }
462

463
  @Override
464
  public Path getSettingsPath() {
465

466
    return this.settingsPath;
3✔
467
  }
468

469
  @Override
470
  public Path getSettingsGitRepository() {
471

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

481
  @Override
482
  public boolean isSettingsRepositorySymlinkOrJunction() {
483

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

491
  @Override
492
  public Path getSettingsCommitIdPath() {
493

494
    return this.settingsCommitIdPath;
3✔
495
  }
496

497
  @Override
498
  public Path getConfPath() {
499

500
    return this.confPath;
3✔
501
  }
502

503
  @Override
504
  public Path getSoftwarePath() {
505

506
    if (this.ideHome == null) {
3✔
507
      return null;
2✔
508
    }
509
    return this.ideHome.resolve(FOLDER_SOFTWARE);
5✔
510
  }
511

512
  @Override
513
  public Path getSoftwareExtraPath() {
514

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

522
  @Override
523
  public Path getSoftwareRepositoryPath() {
524

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

532
  @Override
533
  public Path getPluginsPath() {
534

535
    return this.pluginsPath;
3✔
536
  }
537

538
  @Override
539
  public String getWorkspaceName() {
540

541
    return this.workspaceName;
3✔
542
  }
543

544
  @Override
545
  public Path getWorkspacePath() {
546

547
    return this.workspacePath;
3✔
548
  }
549

550
  @Override
551
  public Path getDownloadPath() {
552

553
    return this.downloadPath;
3✔
554
  }
555

556
  @Override
557
  public Path getUrlsPath() {
558

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

566
  @Override
567
  public Path getToolRepositoryPath() {
568

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

576
  @Override
577
  public SystemPath getPath() {
578

579
    return this.path;
3✔
580
  }
581

582
  @Override
583
  public EnvironmentVariables getVariables() {
584

585
    if (this.variables == null) {
3✔
586
      this.variables = createVariables();
4✔
587
    }
588
    return this.variables;
3✔
589
  }
590

591
  @Override
592
  public UrlMetadata getUrls() {
593

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

603
  @Override
604
  public boolean isQuietMode() {
605

606
    return this.startContext.isQuietMode();
4✔
607
  }
608

609
  @Override
610
  public boolean isBatchMode() {
611

612
    return this.startContext.isBatchMode();
4✔
613
  }
614

615
  @Override
616
  public boolean isForceMode() {
617

618
    return this.startContext.isForceMode();
4✔
619
  }
620

621
  @Override
622
  public boolean isForcePull() {
623

624
    return this.startContext.isForcePull();
×
625
  }
626

627
  @Override
628
  public boolean isForcePlugins() {
629

630
    return this.startContext.isForcePlugins();
×
631
  }
632

633
  @Override
634
  public boolean isForceRepositories() {
635

636
    return this.startContext.isForceRepositories();
×
637
  }
638

639
  @Override
640
  public boolean isOfflineMode() {
641

642
    return this.startContext.isOfflineMode();
4✔
643
  }
644

645
  @Override
646
  public boolean isSkipUpdatesMode() {
647

648
    return this.startContext.isSkipUpdatesMode();
4✔
649
  }
650

651
  @Override
652
  public boolean isOnline() {
653

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

673
  private void configureNetworkProxy() {
674

675
    if (this.networkProxy == null) {
3✔
676
      this.networkProxy = new NetworkProxy(this);
6✔
677
      this.networkProxy.configure();
3✔
678
    }
679
  }
1✔
680

681
  @Override
682
  public Locale getLocale() {
683

684
    Locale locale = this.startContext.getLocale();
4✔
685
    if (locale == null) {
2✔
686
      locale = Locale.getDefault();
2✔
687
    }
688
    return locale;
2✔
689
  }
690

691
  @Override
692
  public DirectoryMerger getWorkspaceMerger() {
693

694
    if (this.workspaceMerger == null) {
3✔
695
      this.workspaceMerger = new DirectoryMerger(this);
6✔
696
    }
697
    return this.workspaceMerger;
3✔
698
  }
699

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

706
    return this.defaultExecutionDirectory;
×
707
  }
708

709
  /**
710
   * @param defaultExecutionDirectory new value of {@link #getDefaultExecutionDirectory()}.
711
   */
712
  public void setDefaultExecutionDirectory(Path defaultExecutionDirectory) {
713

714
    if (defaultExecutionDirectory != null) {
×
715
      this.defaultExecutionDirectory = defaultExecutionDirectory;
×
716
    }
717
  }
×
718

719
  @Override
720
  public GitContext getGitContext() {
721

722
    return new GitContextImpl(this);
×
723
  }
724

725
  @Override
726
  public ProcessContext newProcess() {
727

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

735
  @Override
736
  public IdeSystem getSystem() {
737

738
    if (this.system == null) {
×
739
      this.system = new IdeSystemImpl(this);
×
740
    }
741
    return this.system;
×
742
  }
743

744
  /**
745
   * @return a new instance of {@link ProcessContext}.
746
   * @see #newProcess()
747
   */
748
  protected ProcessContext createProcessContext() {
749

750
    return new ProcessContextImpl(this);
5✔
751
  }
752

753
  @Override
754
  public IdeSubLogger level(IdeLogLevel level) {
755

756
    return this.startContext.level(level);
5✔
757
  }
758

759
  @Override
760
  public void logIdeHomeAndRootStatus() {
761

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

772
  @Override
773
  public String askForInput(String message, String defaultValue) {
774

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

789
  @Override
790
  public String askForInput(String message) {
791

792
    String input;
793
    do {
794
      info(message);
3✔
795
      input = readLine().trim();
4✔
796
    } while (input.isEmpty());
3!
797

798
    return input;
2✔
799
  }
800

801
  @SuppressWarnings("unchecked")
802
  @Override
803
  public <O> O question(String question, O... options) {
804

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

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

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

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

852
  @Override
853
  public Step getCurrentStep() {
854

855
    return this.currentStep;
×
856
  }
857

858
  @Override
859
  public StepImpl newStep(boolean silent, String name, Object... parameters) {
860

861
    this.currentStep = new StepImpl(this, this.currentStep, name, silent, parameters);
11✔
862
    return this.currentStep;
3✔
863
  }
864

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

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

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

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

931
  @Override
932
  public void runWithoutLogging(Runnable lambda, IdeLogLevel threshold) {
933

934
    this.startContext.deactivateLogging(threshold);
4✔
935
    lambda.run();
2✔
936
    this.startContext.activateLogging();
3✔
937
  }
1✔
938

939
  /**
940
   * @param cmd the potential {@link Commandlet} to {@link #apply(CliArguments, Commandlet) apply} and {@link Commandlet#run() run}.
941
   * @return {@code true} if the given {@link Commandlet} matched and did {@link Commandlet#run() run} successfully, {@code false} otherwise (the
942
   *     {@link Commandlet} did not match and we have to try a different candidate).
943
   */
944
  private ValidationResult applyAndRun(CliArguments arguments, Commandlet cmd) {
945

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

981
              } else {
982
                interaction(
×
983
                    "Updates are available for the settings repository. If you want to apply the latest changes, call \"ide update\"");
984
              }
985
            }
986
          }
987
        }
988
        boolean success = ensureLicenseAgreement(cmd);
4✔
989
        if (!success) {
2!
990
          return ValidationResultValid.get();
×
991
        }
992
        cmd.run();
2✔
993
      } finally {
994
        if (previousLogLevel != null) {
2!
995
          this.startContext.setLogLevel(previousLogLevel);
×
996
        }
997
      }
1✔
998
    } else {
999
      trace("Commandlet did not match");
×
1000
    }
1001
    return result;
2✔
1002
  }
1003

1004
  private boolean ensureLicenseAgreement(Commandlet cmd) {
1005

1006
    if (isTest()) {
3!
1007
      return true; // ignore for tests
2✔
1008
    }
1009
    getFileAccess().mkdirs(this.userHomeIde);
×
1010
    Path licenseAgreement = this.userHomeIde.resolve(FILE_LICENSE_AGREEMENT);
×
1011
    if (Files.isRegularFile(licenseAgreement)) {
×
1012
      return true; // success, license already accepted
×
1013
    }
1014
    if (cmd instanceof EnvironmentCommandlet) {
×
1015
      // 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
1016
      // 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
1017
      // printing anything anymore in such case.
1018
      return false;
×
1019
    }
1020
    boolean logLevelInfoDisabled = !this.startContext.info().isEnabled();
×
1021
    if (logLevelInfoDisabled) {
×
1022
      this.startContext.setLogLevel(IdeLogLevel.INFO, true);
×
1023
    }
1024
    boolean logLevelInteractionDisabled = !this.startContext.interaction().isEnabled();
×
1025
    if (logLevelInteractionDisabled) {
×
1026
      this.startContext.setLogLevel(IdeLogLevel.INTERACTION, true);
×
1027
    }
1028
    StringBuilder sb = new StringBuilder(1180);
×
1029
    sb.append(LOGO).append("""
×
1030
        Welcome to IDEasy!
1031
        This product (with its included 3rd party components) is open-source software and can be used free (also commercially).
1032
        It supports automatic download and installation of arbitrary 3rd party tools.
1033
        By default only open-source 3rd party tools are used (downloaded, installed, executed).
1034
        But if explicitly configured, also commercial software that requires an additional license may be used.
1035
        This happens e.g. if you configure "ultimate" edition of IntelliJ or "docker" edition of Docker (Docker Desktop).
1036
        You are solely responsible for all risks implied by using this software.
1037
        Before using IDEasy you need to read and accept the license agreement with all involved licenses.
1038
        You will be able to find it online under the following URL:
1039
        """).append(LICENSE_URL);
×
1040
    if (this.ideRoot != null) {
×
1041
      sb.append("\n\nAlso it is included in the documentation that you can find here:\n").
×
1042
          append(getIdePath().resolve("IDEasy.pdf").toString()).append("\n");
×
1043
    }
1044
    info(sb.toString());
×
1045
    askToContinue("Do you accept these terms of use and all license agreements?");
×
1046

1047
    sb.setLength(0);
×
1048
    LocalDateTime now = LocalDateTime.now();
×
1049
    sb.append("On ").append(DateTimeUtil.formatDate(now, false)).append(" at ").append(DateTimeUtil.formatTime(now))
×
1050
        .append(" you accepted the IDEasy license.\n").append(LICENSE_URL);
×
1051
    try {
1052
      Files.writeString(licenseAgreement, sb);
×
1053
    } catch (Exception e) {
×
1054
      throw new RuntimeException("Failed to save license agreement!", e);
×
1055
    }
×
1056
    if (logLevelInfoDisabled) {
×
1057
      this.startContext.setLogLevel(IdeLogLevel.INFO, false);
×
1058
    }
1059
    if (logLevelInteractionDisabled) {
×
1060
      this.startContext.setLogLevel(IdeLogLevel.INTERACTION, false);
×
1061
    }
1062
    return true;
×
1063
  }
1064

1065
  private void verifyIdeRoot() {
1066

1067
    if (!isTest()) {
3!
1068
      if (this.ideRoot == null) {
×
1069
        warning("Variable IDE_ROOT is undefined. Please check your installation or run setup script again.");
×
1070
      } else if (this.ideHome != null) {
×
1071
        Path ideRootPath = getIdeRootPathFromEnv();
×
1072
        if (!this.ideRoot.equals(ideRootPath)) {
×
1073
          warning("Variable IDE_ROOT is set to '{}' but for your project '{}' the path '{}' would have been expected.", ideRootPath,
×
1074
              this.ideHome.getFileName(), this.ideRoot);
×
1075
        }
1076
      }
1077
    }
1078
  }
1✔
1079

1080
  @Override
1081
  public void verifyIdeMinVersion(boolean throwException) {
1082
    VersionIdentifier minVersion = IDE_MIN_VERSION.get(this);
5✔
1083
    if (minVersion == null) {
2✔
1084
      return;
1✔
1085
    }
1086
    if (IdeVersion.getVersionIdentifier().compareVersion(minVersion).isLess()) {
5✔
1087
      String message = String.format("Your version of IDEasy is currently %s\n"
7✔
1088
          + "However, this is too old as your project requires at latest version %s\n"
1089
          + "Please run the following command to update to the latest version of IDEasy and fix the problem:\n"
1090
          + "ide upgrade", IdeVersion.getVersionIdentifier().toString(), minVersion.toString());
8✔
1091
      if (throwException) {
2✔
1092
        throw new CliException(message);
5✔
1093
      } else {
1094
        warning(message);
3✔
1095
      }
1096
    }
1097
  }
1✔
1098

1099
  /**
1100
   * @param arguments the {@link CliArguments#ofCompletion(String...) completion arguments}.
1101
   * @param includeContextOptions to include the options of {@link ContextCommandlet}.
1102
   * @return the {@link List} of {@link CompletionCandidate}s to suggest.
1103
   */
1104
  public List<CompletionCandidate> complete(CliArguments arguments, boolean includeContextOptions) {
1105

1106
    CompletionCandidateCollector collector = new CompletionCandidateCollectorDefault(this);
5✔
1107
    if (arguments.current().isStart()) {
4✔
1108
      arguments.next();
3✔
1109
    }
1110
    if (includeContextOptions) {
2✔
1111
      ContextCommandlet cc = new ContextCommandlet();
4✔
1112
      for (Property<?> property : cc.getProperties()) {
11✔
1113
        assert (property.isOption());
4!
1114
        property.apply(arguments, this, cc, collector);
7✔
1115
      }
1✔
1116
    }
1117
    Iterator<Commandlet> commandletIterator = this.commandletManager.findCommandlet(arguments, collector);
6✔
1118
    CliArgument current = arguments.current();
3✔
1119
    if (current.isCompletion() && current.isCombinedShortOption()) {
6✔
1120
      collector.add(current.get(), null, null, null);
7✔
1121
    }
1122
    arguments.next();
3✔
1123
    while (commandletIterator.hasNext()) {
3✔
1124
      Commandlet cmd = commandletIterator.next();
4✔
1125
      if (!arguments.current().isEnd()) {
4✔
1126
        completeCommandlet(arguments.copy(), cmd, collector);
6✔
1127
      }
1128
    }
1✔
1129
    return collector.getSortedCandidates();
3✔
1130
  }
1131

1132
  private void completeCommandlet(CliArguments arguments, Commandlet cmd, CompletionCandidateCollector collector) {
1133

1134
    trace("Trying to match arguments for auto-completion for commandlet {}", cmd.getName());
10✔
1135
    Iterator<Property<?>> valueIterator = cmd.getValues().iterator();
4✔
1136
    valueIterator.next(); // skip first property since this is the keyword property that already matched to find the commandlet
3✔
1137
    List<Property<?>> properties = cmd.getProperties();
3✔
1138
    // we are creating our own list of options and remove them when matched to avoid duplicate suggestions
1139
    List<Property<?>> optionProperties = new ArrayList<>(properties.size());
6✔
1140
    for (Property<?> property : properties) {
10✔
1141
      if (property.isOption()) {
3✔
1142
        optionProperties.add(property);
4✔
1143
      }
1144
    }
1✔
1145
    CliArgument currentArgument = arguments.current();
3✔
1146
    while (!currentArgument.isEnd()) {
3✔
1147
      trace("Trying to match argument '{}'", currentArgument);
9✔
1148
      if (currentArgument.isOption() && !arguments.isEndOptions()) {
6!
1149
        if (currentArgument.isCompletion()) {
3✔
1150
          Iterator<Property<?>> optionIterator = optionProperties.iterator();
3✔
1151
          while (optionIterator.hasNext()) {
3✔
1152
            Property<?> option = optionIterator.next();
4✔
1153
            boolean success = option.apply(arguments, this, cmd, collector);
7✔
1154
            if (success) {
2✔
1155
              optionIterator.remove();
2✔
1156
              arguments.next();
3✔
1157
            }
1158
          }
1✔
1159
        } else {
1✔
1160
          Property<?> option = cmd.getOption(currentArgument.get());
5✔
1161
          if (option != null) {
2✔
1162
            arguments.next();
3✔
1163
            boolean removed = optionProperties.remove(option);
4✔
1164
            if (!removed) {
2!
1165
              option = null;
×
1166
            }
1167
          }
1168
          if (option == null) {
2✔
1169
            trace("No such option was found.");
3✔
1170
            return;
1✔
1171
          }
1172
        }
1✔
1173
      } else {
1174
        if (valueIterator.hasNext()) {
3✔
1175
          Property<?> valueProperty = valueIterator.next();
4✔
1176
          boolean success = valueProperty.apply(arguments, this, cmd, collector);
7✔
1177
          if (!success) {
2✔
1178
            trace("Completion cannot match any further.");
3✔
1179
            return;
1✔
1180
          }
1181
        } else {
1✔
1182
          trace("No value left for completion.");
3✔
1183
          return;
1✔
1184
        }
1185
      }
1186
      currentArgument = arguments.current();
4✔
1187
    }
1188
  }
1✔
1189

1190
  /**
1191
   * @param arguments the {@link CliArguments} to apply. Will be {@link CliArguments#next() consumed} as they are matched. Consider passing a
1192
   *     {@link CliArguments#copy() copy} as needed.
1193
   * @param cmd the potential {@link Commandlet} to match.
1194
   * @return the {@link ValidationResult} telling if the {@link CliArguments} can be applied successfully or if validation errors ocurred.
1195
   */
1196
  public ValidationResult apply(CliArguments arguments, Commandlet cmd) {
1197

1198
    trace("Trying to match arguments to commandlet {}", cmd.getName());
10✔
1199
    CliArgument currentArgument = arguments.current();
3✔
1200
    Iterator<Property<?>> propertyIterator = cmd.getValues().iterator();
4✔
1201
    Property<?> property = null;
2✔
1202
    if (propertyIterator.hasNext()) {
3!
1203
      property = propertyIterator.next();
4✔
1204
    }
1205
    while (!currentArgument.isEnd()) {
3✔
1206
      trace("Trying to match argument '{}'", currentArgument);
9✔
1207
      Property<?> currentProperty = property;
2✔
1208
      if (!arguments.isEndOptions()) {
3!
1209
        Property<?> option = cmd.getOption(currentArgument.getKey());
5✔
1210
        if (option != null) {
2!
1211
          currentProperty = option;
×
1212
        }
1213
      }
1214
      if (currentProperty == null) {
2!
1215
        trace("No option or next value found");
×
1216
        ValidationState state = new ValidationState(null);
×
1217
        state.addErrorMessage("No matching property found");
×
1218
        return state;
×
1219
      }
1220
      trace("Next property candidate to match argument is {}", currentProperty);
9✔
1221
      if (currentProperty == property) {
3!
1222
        if (!property.isMultiValued()) {
3✔
1223
          if (propertyIterator.hasNext()) {
3✔
1224
            property = propertyIterator.next();
5✔
1225
          } else {
1226
            property = null;
2✔
1227
          }
1228
        }
1229
        if ((property != null) && property.isValue() && property.isMultiValued()) {
8!
1230
          arguments.stopSplitShortOptions();
2✔
1231
        }
1232
      }
1233
      boolean matches = currentProperty.apply(arguments, this, cmd, null);
7✔
1234
      if (!matches) {
2!
1235
        ValidationState state = new ValidationState(null);
×
1236
        state.addErrorMessage("No matching property found");
×
1237
        return state;
×
1238
      }
1239
      currentArgument = arguments.current();
3✔
1240
    }
1✔
1241
    return ValidationResultValid.get();
2✔
1242
  }
1243

1244
  @Override
1245
  public String findBash() {
1246

1247
    String bash = "bash";
2✔
1248
    if (SystemInfoImpl.INSTANCE.isWindows()) {
3!
1249
      bash = findBashOnWindows();
×
1250
    }
1251

1252
    return bash;
2✔
1253
  }
1254

1255
  private String findBashOnWindows() {
1256

1257
    // Check if Git Bash exists in the default location
1258
    Path defaultPath = Path.of("C:\\Program Files\\Git\\bin\\bash.exe");
×
1259
    if (Files.exists(defaultPath)) {
×
1260
      return defaultPath.toString();
×
1261
    }
1262

1263
    // If not found in the default location, try the registry query
1264
    String[] bashVariants = { "GitForWindows", "Cygwin\\setup" };
×
1265
    String[] registryKeys = { "HKEY_LOCAL_MACHINE", "HKEY_CURRENT_USER" };
×
1266
    String regQueryResult;
1267
    for (String bashVariant : bashVariants) {
×
1268
      for (String registryKey : registryKeys) {
×
1269
        String toolValueName = ("GitForWindows".equals(bashVariant)) ? "InstallPath" : "rootdir";
×
1270
        String command = "reg query " + registryKey + "\\Software\\" + bashVariant + "  /v " + toolValueName + " 2>nul";
×
1271

1272
        try {
1273
          Process process = new ProcessBuilder("cmd.exe", "/c", command).start();
×
1274
          try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
×
1275
            StringBuilder output = new StringBuilder();
×
1276
            String line;
1277

1278
            while ((line = reader.readLine()) != null) {
×
1279
              output.append(line);
×
1280
            }
1281

1282
            int exitCode = process.waitFor();
×
1283
            if (exitCode != 0) {
×
1284
              return null;
×
1285
            }
1286

1287
            regQueryResult = output.toString();
×
1288
            if (regQueryResult != null) {
×
1289
              int index = regQueryResult.indexOf("REG_SZ");
×
1290
              if (index != -1) {
×
1291
                String path = regQueryResult.substring(index + "REG_SZ".length()).trim();
×
1292
                return path + "\\bin\\bash.exe";
×
1293
              }
1294
            }
1295

1296
          }
×
1297
        } catch (Exception e) {
×
1298
          return null;
×
1299
        }
×
1300
      }
1301
    }
1302
    // no bash found
1303
    return null;
×
1304
  }
1305

1306
  @Override
1307
  public WindowsPathSyntax getPathSyntax() {
1308

1309
    return this.pathSyntax;
3✔
1310
  }
1311

1312
  /**
1313
   * @param pathSyntax new value of {@link #getPathSyntax()}.
1314
   */
1315
  public void setPathSyntax(WindowsPathSyntax pathSyntax) {
1316

1317
    this.pathSyntax = pathSyntax;
3✔
1318
  }
1✔
1319

1320
  /**
1321
   * @return the {@link IdeStartContextImpl}.
1322
   */
1323
  public IdeStartContextImpl getStartContext() {
1324

1325
    return startContext;
3✔
1326
  }
1327

1328
  /**
1329
   * @return the {@link WindowsHelper}.
1330
   */
1331
  public final WindowsHelper getWindowsHelper() {
1332

1333
    if (this.windowsHelper == null) {
3✔
1334
      this.windowsHelper = createWindowsHelper();
4✔
1335
    }
1336
    return this.windowsHelper;
3✔
1337
  }
1338

1339
  /**
1340
   * @return the new {@link WindowsHelper} instance.
1341
   */
1342
  protected WindowsHelper createWindowsHelper() {
1343

1344
    return new WindowsHelperImpl(this);
×
1345
  }
1346

1347
  /**
1348
   * Reloads this context and re-initializes the {@link #getVariables() variables}.
1349
   */
1350
  public void reload() {
1351

1352
    this.variables = null;
3✔
1353
    this.customToolRepository = null;
3✔
1354
  }
1✔
1355

1356
  @Override
1357
  public void writeVersionFile(VersionIdentifier version, Path installationPath) {
1358

1359
    assert (Files.isDirectory(installationPath));
6!
1360
    Path versionFile = installationPath.resolve(FILE_SOFTWARE_VERSION);
4✔
1361
    getFileAccess().writeFileContent(version.toString(), versionFile);
6✔
1362
  }
1✔
1363

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