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

devonfw / IDEasy / 14661121862

25 Apr 2025 09:11AM UTC coverage: 67.586% (+0.1%) from 67.448%
14661121862

Pull #1262

github

web-flow
Merge bb585752e into af5a7ba20
Pull Request #1262: #793: add support for IDE_MIN_VERSION

3089 of 4976 branches covered (62.08%)

Branch coverage included in aggregate %.

7935 of 11335 relevant lines covered (70.0%)

3.06 hits per line

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

61.61
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
  /**
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
    this.mavenRepository = new MavenRepository(this);
6✔
205
  }
1✔
206

207
  private Path findIdeRoot(Path ideHomePath) {
208

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

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

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

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

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

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

267
  private String getMessageIdeHomeFound() {
268

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

272
  private String getMessageNotInsideIdeProject() {
273

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

277
  private String getMessageIdeRootNotFound() {
278

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

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

292
    return false;
×
293
  }
294

295
  protected SystemPath computeSystemPath() {
296

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

300
  private boolean isIdeHome(Path dir) {
301

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

310
  private EnvironmentVariables createVariables() {
311

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

320
  protected AbstractEnvironmentVariables createSystemVariables() {
321

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

325
  @Override
326
  public SystemInfo getSystemInfo() {
327

328
    return this.systemInfo;
3✔
329
  }
330

331
  @Override
332
  public FileAccess getFileAccess() {
333

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

339
  @Override
340
  public CommandletManager getCommandletManager() {
341

342
    return this.commandletManager;
3✔
343
  }
344

345
  @Override
346
  public ToolRepository getDefaultToolRepository() {
347

348
    return this.defaultToolRepository;
3✔
349
  }
350

351
  @Override
352
  public MavenRepository getMavenToolRepository() {
353

354
    return this.mavenRepository;
3✔
355
  }
356

357
  @Override
358
  public CustomToolRepository getCustomToolRepository() {
359

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

366
  @Override
367
  public Path getIdeHome() {
368

369
    return this.ideHome;
3✔
370
  }
371

372
  @Override
373
  public String getProjectName() {
374

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

381
  @Override
382
  public VersionIdentifier getProjectVersion() {
383

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

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

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

405
  @Override
406
  public Path getIdeRoot() {
407

408
    return this.ideRoot;
3✔
409
  }
410

411
  @Override
412
  public Path getIdePath() {
413

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

421
  @Override
422
  public Path getCwd() {
423

424
    return this.cwd;
3✔
425
  }
426

427
  @Override
428
  public Path getTempPath() {
429

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

437
  @Override
438
  public Path getTempDownloadPath() {
439

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

447
  @Override
448
  public Path getUserHome() {
449

450
    return this.userHome;
3✔
451
  }
452

453
  @Override
454
  public Path getUserHomeIde() {
455

456
    return this.userHomeIde;
3✔
457
  }
458

459
  @Override
460
  public Path getSettingsPath() {
461

462
    return this.settingsPath;
3✔
463
  }
464

465
  @Override
466
  public Path getSettingsGitRepository() {
467

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

477
  @Override
478
  public boolean isSettingsRepositorySymlinkOrJunction() {
479

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

487
  @Override
488
  public Path getSettingsCommitIdPath() {
489

490
    return this.settingsCommitIdPath;
3✔
491
  }
492

493
  @Override
494
  public Path getConfPath() {
495

496
    return this.confPath;
3✔
497
  }
498

499
  @Override
500
  public Path getSoftwarePath() {
501

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

508
  @Override
509
  public Path getSoftwareExtraPath() {
510

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

518
  @Override
519
  public Path getSoftwareRepositoryPath() {
520

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

528
  @Override
529
  public Path getPluginsPath() {
530

531
    return this.pluginsPath;
3✔
532
  }
533

534
  @Override
535
  public String getWorkspaceName() {
536

537
    return this.workspaceName;
3✔
538
  }
539

540
  @Override
541
  public Path getWorkspacePath() {
542

543
    return this.workspacePath;
3✔
544
  }
545

546
  @Override
547
  public Path getDownloadPath() {
548

549
    return this.downloadPath;
3✔
550
  }
551

552
  @Override
553
  public Path getUrlsPath() {
554

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

562
  @Override
563
  public Path getToolRepositoryPath() {
564

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

572
  @Override
573
  public SystemPath getPath() {
574

575
    return this.path;
3✔
576
  }
577

578
  @Override
579
  public EnvironmentVariables getVariables() {
580

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

587
  @Override
588
  public UrlMetadata getUrls() {
589

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

599
  @Override
600
  public boolean isQuietMode() {
601

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

605
  @Override
606
  public boolean isBatchMode() {
607

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

611
  @Override
612
  public boolean isForceMode() {
613

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

617
  @Override
618
  public boolean isForcePull() {
619

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

623
  @Override
624
  public boolean isForcePlugins() {
625

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

629
  @Override
630
  public boolean isForceRepositories() {
631

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

635
  @Override
636
  public boolean isOfflineMode() {
637

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

641
  @Override
642
  public boolean isSkipUpdatesMode() {
643

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

647
  @Override
648
  public boolean isOnline() {
649

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

669
  private void configureNetworkProxy() {
670

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

677
  @Override
678
  public Locale getLocale() {
679

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

687
  @Override
688
  public DirectoryMerger getWorkspaceMerger() {
689

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

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

702
    return this.defaultExecutionDirectory;
×
703
  }
704

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

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

715
  @Override
716
  public GitContext getGitContext() {
717

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

721
  @Override
722
  public ProcessContext newProcess() {
723

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

731
  @Override
732
  public IdeSystem getSystem() {
733

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

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

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

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

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

755
  @Override
756
  public void logIdeHomeAndRootStatus() {
757

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

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

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

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

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

794
    return input;
2✔
795
  }
796

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

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

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

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

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

848
  @Override
849
  public Step getCurrentStep() {
850

851
    return this.currentStep;
×
852
  }
853

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

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

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

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

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

887
    CliArgument current = arguments.current();
3✔
888
    assert (this.currentStep == null);
4!
889
    boolean supressStepSuccess = false;
2✔
890
    StepImpl step = newStep(true, "ide", (Object[]) current.asArray());
8✔
891
    Iterator<Commandlet> commandletIterator = this.commandletManager.findCommandlet(arguments, null);
6✔
892
    Commandlet cmd = null;
2✔
893
    ValidationResult result = null;
2✔
894
    try {
895
      while (commandletIterator.hasNext()) {
3✔
896
        cmd = commandletIterator.next();
4✔
897
        result = applyAndRun(arguments.copy(), cmd);
6✔
898
        if (result.isValid()) {
3!
899
          supressStepSuccess = cmd.isSuppressStepSuccess();
3✔
900
          step.success();
2✔
901
          return ProcessResult.SUCCESS;
4✔
902
        }
903
      }
904
      this.startContext.activateLogging();
3✔
905
      verifyIdeMinVersion(false);
3✔
906
      if (result != null) {
2!
907
        error(result.getErrorMessage());
×
908
      }
909
      step.error("Invalid arguments: {}", current.getArgs());
10✔
910
      HelpCommandlet help = this.commandletManager.getCommandlet(HelpCommandlet.class);
6✔
911
      if (cmd != null) {
2!
912
        help.commandlet.setValue(cmd);
×
913
      }
914
      help.run();
2✔
915
      return 1;
4✔
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
  public void verifyIdeMinVersion(boolean throwException) {
1069
    if (IDE_MIN_VERSION.get(this) == null) {
4✔
1070
      return;
1✔
1071
    }
1072
    if (IdeVersion.getVersionIdentifier().compareVersion(IDE_MIN_VERSION.get(this)).isLess()) {
8✔
1073
      String message = String.format("Your version of IDEasy is currently %s\n"
7✔
1074
          + "However, this is too old as your project requires at latest version %s\n"
1075
          + "Please run the following command to update to the latest version of IDEasy and fix the problem:\n"
1076
          + "ide upgrade", IdeVersion.getVersionIdentifier().toString(), IDE_MIN_VERSION.get(this).toString());
11✔
1077
      if (throwException) {
2✔
1078
        throw new CliException(message);
5✔
1079
      } else {
1080
        warning(message);
3✔
1081
      }
1082
    }
1083
  }
1✔
1084

1085
  /**
1086
   * @param arguments the {@link CliArguments#ofCompletion(String...) completion arguments}.
1087
   * @param includeContextOptions to include the options of {@link ContextCommandlet}.
1088
   * @return the {@link List} of {@link CompletionCandidate}s to suggest.
1089
   */
1090
  public List<CompletionCandidate> complete(CliArguments arguments, boolean includeContextOptions) {
1091

1092
    CompletionCandidateCollector collector = new CompletionCandidateCollectorDefault(this);
5✔
1093
    if (arguments.current().isStart()) {
4✔
1094
      arguments.next();
3✔
1095
    }
1096
    if (includeContextOptions) {
2✔
1097
      ContextCommandlet cc = new ContextCommandlet();
4✔
1098
      for (Property<?> property : cc.getProperties()) {
11✔
1099
        assert (property.isOption());
4!
1100
        property.apply(arguments, this, cc, collector);
7✔
1101
      }
1✔
1102
    }
1103
    Iterator<Commandlet> commandletIterator = this.commandletManager.findCommandlet(arguments, collector);
6✔
1104
    CliArgument current = arguments.current();
3✔
1105
    if (current.isCompletion() && current.isCombinedShortOption()) {
6✔
1106
      collector.add(current.get(), null, null, null);
7✔
1107
    }
1108
    arguments.next();
3✔
1109
    while (commandletIterator.hasNext()) {
3✔
1110
      Commandlet cmd = commandletIterator.next();
4✔
1111
      if (!arguments.current().isEnd()) {
4✔
1112
        completeCommandlet(arguments.copy(), cmd, collector);
6✔
1113
      }
1114
    }
1✔
1115
    return collector.getSortedCandidates();
3✔
1116
  }
1117

1118
  private void completeCommandlet(CliArguments arguments, Commandlet cmd, CompletionCandidateCollector collector) {
1119

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

1176
  /**
1177
   * @param arguments the {@link CliArguments} to apply. Will be {@link CliArguments#next() consumed} as they are matched. Consider passing a
1178
   *     {@link CliArguments#copy() copy} as needed.
1179
   * @param cmd the potential {@link Commandlet} to match.
1180
   * @return the {@link ValidationResult} telling if the {@link CliArguments} can be applied successfully or if validation errors ocurred.
1181
   */
1182
  public ValidationResult apply(CliArguments arguments, Commandlet cmd) {
1183

1184
    trace("Trying to match arguments to commandlet {}", cmd.getName());
10✔
1185
    CliArgument currentArgument = arguments.current();
3✔
1186
    Iterator<Property<?>> propertyIterator = cmd.getValues().iterator();
4✔
1187
    Property<?> property = null;
2✔
1188
    if (propertyIterator.hasNext()) {
3!
1189
      property = propertyIterator.next();
4✔
1190
    }
1191
    while (!currentArgument.isEnd()) {
3✔
1192
      trace("Trying to match argument '{}'", currentArgument);
9✔
1193
      Property<?> currentProperty = property;
2✔
1194
      if (!arguments.isEndOptions()) {
3!
1195
        Property<?> option = cmd.getOption(currentArgument.getKey());
5✔
1196
        if (option != null) {
2!
1197
          currentProperty = option;
×
1198
        }
1199
      }
1200
      if (currentProperty == null) {
2!
1201
        trace("No option or next value found");
×
1202
        ValidationState state = new ValidationState(null);
×
1203
        state.addErrorMessage("No matching property found");
×
1204
        return state;
×
1205
      }
1206
      trace("Next property candidate to match argument is {}", currentProperty);
9✔
1207
      if (currentProperty == property) {
3!
1208
        if (!property.isMultiValued()) {
3✔
1209
          if (propertyIterator.hasNext()) {
3✔
1210
            property = propertyIterator.next();
5✔
1211
          } else {
1212
            property = null;
2✔
1213
          }
1214
        }
1215
        if ((property != null) && property.isValue() && property.isMultiValued()) {
8!
1216
          arguments.stopSplitShortOptions();
2✔
1217
        }
1218
      }
1219
      boolean matches = currentProperty.apply(arguments, this, cmd, null);
7✔
1220
      if (!matches) {
2!
1221
        ValidationState state = new ValidationState(null);
×
1222
        state.addErrorMessage("No matching property found");
×
1223
        return state;
×
1224
      }
1225
      currentArgument = arguments.current();
3✔
1226
    }
1✔
1227
    return ValidationResultValid.get();
2✔
1228
  }
1229

1230
  @Override
1231
  public String findBash() {
1232

1233
    String bash = "bash";
2✔
1234
    if (SystemInfoImpl.INSTANCE.isWindows()) {
3!
1235
      bash = findBashOnWindows();
×
1236
    }
1237

1238
    return bash;
2✔
1239
  }
1240

1241
  private String findBashOnWindows() {
1242

1243
    // Check if Git Bash exists in the default location
1244
    Path defaultPath = Path.of("C:\\Program Files\\Git\\bin\\bash.exe");
×
1245
    if (Files.exists(defaultPath)) {
×
1246
      return defaultPath.toString();
×
1247
    }
1248

1249
    // If not found in the default location, try the registry query
1250
    String[] bashVariants = { "GitForWindows", "Cygwin\\setup" };
×
1251
    String[] registryKeys = { "HKEY_LOCAL_MACHINE", "HKEY_CURRENT_USER" };
×
1252
    String regQueryResult;
1253
    for (String bashVariant : bashVariants) {
×
1254
      for (String registryKey : registryKeys) {
×
1255
        String toolValueName = ("GitForWindows".equals(bashVariant)) ? "InstallPath" : "rootdir";
×
1256
        String command = "reg query " + registryKey + "\\Software\\" + bashVariant + "  /v " + toolValueName + " 2>nul";
×
1257

1258
        try {
1259
          Process process = new ProcessBuilder("cmd.exe", "/c", command).start();
×
1260
          try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
×
1261
            StringBuilder output = new StringBuilder();
×
1262
            String line;
1263

1264
            while ((line = reader.readLine()) != null) {
×
1265
              output.append(line);
×
1266
            }
1267

1268
            int exitCode = process.waitFor();
×
1269
            if (exitCode != 0) {
×
1270
              return null;
×
1271
            }
1272

1273
            regQueryResult = output.toString();
×
1274
            if (regQueryResult != null) {
×
1275
              int index = regQueryResult.indexOf("REG_SZ");
×
1276
              if (index != -1) {
×
1277
                String path = regQueryResult.substring(index + "REG_SZ".length()).trim();
×
1278
                return path + "\\bin\\bash.exe";
×
1279
              }
1280
            }
1281

1282
          }
×
1283
        } catch (Exception e) {
×
1284
          return null;
×
1285
        }
×
1286
      }
1287
    }
1288
    // no bash found
1289
    return null;
×
1290
  }
1291

1292
  @Override
1293
  public WindowsPathSyntax getPathSyntax() {
1294

1295
    return this.pathSyntax;
3✔
1296
  }
1297

1298
  /**
1299
   * @param pathSyntax new value of {@link #getPathSyntax()}.
1300
   */
1301
  public void setPathSyntax(WindowsPathSyntax pathSyntax) {
1302

1303
    this.pathSyntax = pathSyntax;
3✔
1304
  }
1✔
1305

1306
  /**
1307
   * @return the {@link IdeStartContextImpl}.
1308
   */
1309
  public IdeStartContextImpl getStartContext() {
1310

1311
    return startContext;
3✔
1312
  }
1313

1314
  /**
1315
   * @return the {@link WindowsHelper}.
1316
   */
1317
  public final WindowsHelper getWindowsHelper() {
1318

1319
    if (this.windowsHelper == null) {
3✔
1320
      this.windowsHelper = createWindowsHelper();
4✔
1321
    }
1322
    return this.windowsHelper;
3✔
1323
  }
1324

1325
  /**
1326
   * @return the new {@link WindowsHelper} instance.
1327
   */
1328
  protected WindowsHelper createWindowsHelper() {
1329

1330
    return new WindowsHelperImpl(this);
×
1331
  }
1332

1333
  /**
1334
   * Reloads this context and re-initializes the {@link #getVariables() variables}.
1335
   */
1336
  public void reload() {
1337

1338
    this.variables = null;
3✔
1339
    this.customToolRepository = null;
3✔
1340
  }
1✔
1341

1342
  @Override
1343
  public void writeVersionFile(VersionIdentifier version, Path installationPath) {
1344

1345
    assert (Files.isDirectory(installationPath));
6!
1346
    Path versionFile = installationPath.resolve(FILE_SOFTWARE_VERSION);
4✔
1347
    getFileAccess().writeFileContent(version.toString(), versionFile);
6✔
1348
  }
1✔
1349

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

© 2025 Coveralls, Inc