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

devonfw / IDEasy / 14313798819

07 Apr 2025 03:55PM UTC coverage: 67.315% (-0.1%) from 67.432%
14313798819

push

github

web-flow
#1007: Implemented granular force options for update command (#1180)

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

3066 of 4964 branches covered (61.76%)

Branch coverage included in aggregate %.

7901 of 11328 relevant lines covered (69.75%)

3.05 hits per line

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

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

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

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

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

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

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

81
  private final IdeStartContextImpl startContext;
82

83
  private Path ideHome;
84

85
  private final Path ideRoot;
86

87
  private Path confPath;
88

89
  protected Path settingsPath;
90

91
  private Path settingsCommitIdPath;
92

93
  protected Path pluginsPath;
94

95
  private Path workspacePath;
96

97
  private String workspaceName;
98

99
  private Path cwd;
100

101
  private Path downloadPath;
102

103
  protected Path userHome;
104

105
  private Path userHomeIde;
106

107
  private SystemPath path;
108

109
  private WindowsPathSyntax pathSyntax;
110

111
  private final SystemInfo systemInfo;
112

113
  private EnvironmentVariables variables;
114

115
  private final FileAccess fileAccess;
116

117
  protected CommandletManager commandletManager;
118

119
  protected ToolRepository defaultToolRepository;
120

121
  private CustomToolRepository customToolRepository;
122

123
  private MavenRepository mavenRepository;
124

125
  private DirectoryMerger workspaceMerger;
126

127
  protected UrlMetadata urlMetadata;
128

129
  protected Path defaultExecutionDirectory;
130

131
  private StepImpl currentStep;
132

133
  protected Boolean online;
134

135
  protected IdeSystem system;
136

137
  private NetworkProxy networkProxy;
138

139
  private WindowsHelper windowsHelper;
140

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

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

186
    // detection completed, initializing variables
187
    this.ideRoot = findIdeRoot(currentDir);
5✔
188

189
    setCwd(workingDirectory, workspace, currentDir);
5✔
190

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

200
    this.defaultToolRepository = new DefaultToolRepository(this);
6✔
201
    this.mavenRepository = new MavenRepository(this);
6✔
202
  }
1✔
203

204
  private Path findIdeRoot(Path ideHomePath) {
205

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

215
  /**
216
   * @return the {@link #getIdeRoot() IDE_ROOT} from the system environment.
217
   */
218
  protected Path getIdeRootPathFromEnv() {
219

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

230
  @Override
231
  public void setCwd(Path userDir, String workspace, Path ideHome) {
232

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

261
    this.path = computeSystemPath();
4✔
262
  }
1✔
263

264
  private String getMessageIdeHomeFound() {
265

266
    return "IDE environment variables have been set for " + this.ideHome + " in workspace " + this.workspaceName;
7✔
267
  }
268

269
  private String getMessageNotInsideIdeProject() {
270

271
    return "You are not inside an IDE project: " + this.cwd;
5✔
272
  }
273

274
  private String getMessageIdeRootNotFound() {
275

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

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

289
    return false;
×
290
  }
291

292
  protected SystemPath computeSystemPath() {
293

294
    return new SystemPath(this);
×
295
  }
296

297
  private boolean isIdeHome(Path dir) {
298

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

307
  private EnvironmentVariables createVariables() {
308

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

317
  protected AbstractEnvironmentVariables createSystemVariables() {
318

319
    return EnvironmentVariables.ofSystem(this);
3✔
320
  }
321

322
  @Override
323
  public SystemInfo getSystemInfo() {
324

325
    return this.systemInfo;
3✔
326
  }
327

328
  @Override
329
  public FileAccess getFileAccess() {
330

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

336
  @Override
337
  public CommandletManager getCommandletManager() {
338

339
    return this.commandletManager;
3✔
340
  }
341

342
  @Override
343
  public ToolRepository getDefaultToolRepository() {
344

345
    return this.defaultToolRepository;
3✔
346
  }
347

348
  @Override
349
  public MavenRepository getMavenToolRepository() {
350

351
    return this.mavenRepository;
3✔
352
  }
353

354
  @Override
355
  public CustomToolRepository getCustomToolRepository() {
356

357
    if (this.customToolRepository == null) {
3!
358
      this.customToolRepository = CustomToolRepositoryImpl.of(this);
4✔
359
    }
360
    return this.customToolRepository;
3✔
361
  }
362

363
  @Override
364
  public Path getIdeHome() {
365

366
    return this.ideHome;
3✔
367
  }
368

369
  @Override
370
  public String getProjectName() {
371

372
    if (this.ideHome != null) {
3!
373
      return this.ideHome.getFileName().toString();
5✔
374
    }
375
    return "";
×
376
  }
377

378
  @Override
379
  public VersionIdentifier getProjectVersion() {
380

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

391
  @Override
392
  public void setProjectVersion(VersionIdentifier version) {
393

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

402
  @Override
403
  public Path getIdeRoot() {
404

405
    return this.ideRoot;
3✔
406
  }
407

408
  @Override
409
  public Path getIdePath() {
410

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

418
  @Override
419
  public Path getCwd() {
420

421
    return this.cwd;
3✔
422
  }
423

424
  @Override
425
  public Path getTempPath() {
426

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

434
  @Override
435
  public Path getTempDownloadPath() {
436

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

444
  @Override
445
  public Path getUserHome() {
446

447
    return this.userHome;
3✔
448
  }
449

450
  @Override
451
  public Path getUserHomeIde() {
452

453
    return this.userHomeIde;
3✔
454
  }
455

456
  @Override
457
  public Path getSettingsPath() {
458

459
    return this.settingsPath;
3✔
460
  }
461

462
  @Override
463
  public Path getSettingsGitRepository() {
464

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

474
  @Override
475
  public boolean isSettingsRepositorySymlinkOrJunction() {
476

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

484
  @Override
485
  public Path getSettingsCommitIdPath() {
486

487
    return this.settingsCommitIdPath;
3✔
488
  }
489

490
  @Override
491
  public Path getConfPath() {
492

493
    return this.confPath;
3✔
494
  }
495

496
  @Override
497
  public Path getSoftwarePath() {
498

499
    if (this.ideHome == null) {
3✔
500
      return null;
2✔
501
    }
502
    return this.ideHome.resolve(FOLDER_SOFTWARE);
5✔
503
  }
504

505
  @Override
506
  public Path getSoftwareExtraPath() {
507

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

515
  @Override
516
  public Path getSoftwareRepositoryPath() {
517

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

525
  @Override
526
  public Path getPluginsPath() {
527

528
    return this.pluginsPath;
3✔
529
  }
530

531
  @Override
532
  public String getWorkspaceName() {
533

534
    return this.workspaceName;
3✔
535
  }
536

537
  @Override
538
  public Path getWorkspacePath() {
539

540
    return this.workspacePath;
3✔
541
  }
542

543
  @Override
544
  public Path getDownloadPath() {
545

546
    return this.downloadPath;
3✔
547
  }
548

549
  @Override
550
  public Path getUrlsPath() {
551

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

559
  @Override
560
  public Path getToolRepositoryPath() {
561

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

569
  @Override
570
  public SystemPath getPath() {
571

572
    return this.path;
3✔
573
  }
574

575
  @Override
576
  public EnvironmentVariables getVariables() {
577

578
    if (this.variables == null) {
3✔
579
      this.variables = createVariables();
4✔
580
    }
581
    return this.variables;
3✔
582
  }
583

584
  @Override
585
  public UrlMetadata getUrls() {
586

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

596
  @Override
597
  public boolean isQuietMode() {
598

599
    return this.startContext.isQuietMode();
4✔
600
  }
601

602
  @Override
603
  public boolean isBatchMode() {
604

605
    return this.startContext.isBatchMode();
×
606
  }
607

608
  @Override
609
  public boolean isForceMode() {
610

611
    return this.startContext.isForceMode();
4✔
612
  }
613

614
  @Override
615
  public boolean isForcePull() {
616

617
    return this.startContext.isForcePull();
×
618
  }
619

620
  @Override
621
  public boolean isForcePlugins() {
622

623
    return this.startContext.isForcePlugins();
×
624
  }
625

626
  @Override
627
  public boolean isForceRepositories() {
628

629
    return this.startContext.isForceRepositories();
×
630
  }
631

632
  @Override
633
  public boolean isOfflineMode() {
634

635
    return this.startContext.isOfflineMode();
4✔
636
  }
637

638
  @Override
639
  public boolean isSkipUpdatesMode() {
640

641
    return this.startContext.isSkipUpdatesMode();
4✔
642
  }
643

644
  @Override
645
  public boolean isOnline() {
646

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

666
  private void configureNetworkProxy() {
667

668
    if (this.networkProxy == null) {
3✔
669
      this.networkProxy = new NetworkProxy(this);
6✔
670
      this.networkProxy.configure();
3✔
671
    }
672
  }
1✔
673

674
  @Override
675
  public Locale getLocale() {
676

677
    Locale locale = this.startContext.getLocale();
4✔
678
    if (locale == null) {
2✔
679
      locale = Locale.getDefault();
2✔
680
    }
681
    return locale;
2✔
682
  }
683

684
  @Override
685
  public DirectoryMerger getWorkspaceMerger() {
686

687
    if (this.workspaceMerger == null) {
3✔
688
      this.workspaceMerger = new DirectoryMerger(this);
6✔
689
    }
690
    return this.workspaceMerger;
3✔
691
  }
692

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

699
    return this.defaultExecutionDirectory;
×
700
  }
701

702
  /**
703
   * @param defaultExecutionDirectory new value of {@link #getDefaultExecutionDirectory()}.
704
   */
705
  public void setDefaultExecutionDirectory(Path defaultExecutionDirectory) {
706

707
    if (defaultExecutionDirectory != null) {
×
708
      this.defaultExecutionDirectory = defaultExecutionDirectory;
×
709
    }
710
  }
×
711

712
  @Override
713
  public GitContext getGitContext() {
714

715
    return new GitContextImpl(this);
×
716
  }
717

718
  @Override
719
  public ProcessContext newProcess() {
720

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

728
  @Override
729
  public IdeSystem getSystem() {
730

731
    if (this.system == null) {
×
732
      this.system = new IdeSystemImpl(this);
×
733
    }
734
    return this.system;
×
735
  }
736

737
  /**
738
   * @return a new instance of {@link ProcessContext}.
739
   * @see #newProcess()
740
   */
741
  protected ProcessContext createProcessContext() {
742

743
    return new ProcessContextImpl(this);
5✔
744
  }
745

746
  @Override
747
  public IdeSubLogger level(IdeLogLevel level) {
748

749
    return this.startContext.level(level);
5✔
750
  }
751

752
  @Override
753
  public void logIdeHomeAndRootStatus() {
754

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

765
  @Override
766
  public String askForInput(String message, String defaultValue) {
767

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

782
  @Override
783
  public String askForInput(String message) {
784

785
    String input;
786
    do {
787
      info(message);
3✔
788
      input = readLine().trim();
4✔
789
    } while (input.isEmpty());
3!
790

791
    return input;
2✔
792
  }
793

794
  @SuppressWarnings("unchecked")
795
  @Override
796
  public <O> O question(String question, O... options) {
797

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

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

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

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

845
  @Override
846
  public Step getCurrentStep() {
847

848
    return this.currentStep;
×
849
  }
850

851
  @Override
852
  public StepImpl newStep(boolean silent, String name, Object... parameters) {
853

854
    this.currentStep = new StepImpl(this, this.currentStep, name, silent, parameters);
11✔
855
    return this.currentStep;
3✔
856
  }
857

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

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

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

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

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

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

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

988
  private boolean ensureLicenseAgreement(Commandlet cmd) {
989

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

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

1049
  private void verifyIdeRoot() {
1050

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

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

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

1097
  private void completeCommandlet(CliArguments arguments, Commandlet cmd, CompletionCandidateCollector collector) {
1098

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

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

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

1209
  @Override
1210
  public String findBash() {
1211

1212
    String bash = "bash";
2✔
1213
    if (SystemInfoImpl.INSTANCE.isWindows()) {
3!
1214
      bash = findBashOnWindows();
×
1215
    }
1216

1217
    return bash;
2✔
1218
  }
1219

1220
  private String findBashOnWindows() {
1221

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

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

1237
        try {
1238
          Process process = new ProcessBuilder("cmd.exe", "/c", command).start();
×
1239
          try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
×
1240
            StringBuilder output = new StringBuilder();
×
1241
            String line;
1242

1243
            while ((line = reader.readLine()) != null) {
×
1244
              output.append(line);
×
1245
            }
1246

1247
            int exitCode = process.waitFor();
×
1248
            if (exitCode != 0) {
×
1249
              return null;
×
1250
            }
1251

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

1261
          }
×
1262
        } catch (Exception e) {
×
1263
          return null;
×
1264
        }
×
1265
      }
1266
    }
1267
    // no bash found
1268
    return null;
×
1269
  }
1270

1271
  @Override
1272
  public WindowsPathSyntax getPathSyntax() {
1273

1274
    return this.pathSyntax;
3✔
1275
  }
1276

1277
  /**
1278
   * @param pathSyntax new value of {@link #getPathSyntax()}.
1279
   */
1280
  public void setPathSyntax(WindowsPathSyntax pathSyntax) {
1281

1282
    this.pathSyntax = pathSyntax;
3✔
1283
  }
1✔
1284

1285
  /**
1286
   * @return the {@link IdeStartContextImpl}.
1287
   */
1288
  public IdeStartContextImpl getStartContext() {
1289

1290
    return startContext;
3✔
1291
  }
1292

1293
  /**
1294
   * @return the {@link WindowsHelper}.
1295
   */
1296
  public final WindowsHelper getWindowsHelper() {
1297

1298
    if (this.windowsHelper == null) {
3✔
1299
      this.windowsHelper = createWindowsHelper();
4✔
1300
    }
1301
    return this.windowsHelper;
3✔
1302
  }
1303

1304
  /**
1305
   * @return the new {@link WindowsHelper} instance.
1306
   */
1307
  protected WindowsHelper createWindowsHelper() {
1308

1309
    return new WindowsHelperImpl(this);
×
1310
  }
1311

1312
  /**
1313
   * Reloads this context and re-initializes the {@link #getVariables() variables}.
1314
   */
1315
  public void reload() {
1316

1317
    this.variables = null;
3✔
1318
    this.customToolRepository = null;
3✔
1319
  }
1✔
1320
}
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